Skip to content

fix: correct IgnoreRuleBindings handling for container monitoring - #839

Merged
matthyx merged 1 commit into
mainfrom
fix/ignore-rule-bindings-unregister
Jun 23, 2026
Merged

fix: correct IgnoreRuleBindings handling for container monitoring#839
matthyx merged 1 commit into
mainfrom
fix/ignore-rule-bindings-unregister

Conversation

@matthyx

@matthyx matthyx commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

What

Corrects how IgnoreRuleBindings is handled across container monitoring and the rule-binding cache.

Background / root cause

In IgnoreRuleBindings mode every pod is evaluated against the full rule set, so ruleManagedPods is never populated (it is only fed from binding notifications). When a container's ApplicationProfile reaches end-of-life, the EOL-driven unregisterContainer tore down monitoring — detection silently stopped after learning completed. Validated on a live cluster (chart sets IGNORERULEBINDINGS=true; with no binding, attack-suite containers were unregistered at profile-EOL and a command-injection produced zero detections).

The fix

The original one-line fix short-circuited the entire unregisterContainer, which also disabled removal of explicitly ignored containers (excludeNamespaces/labels, e.g. kube-system) and cleanup of learning-complete containers when runtime detection is off. This PR scopes the behavior correctly:

  • unregisterContainer — the IgnoreRuleBindings early-return is placed inside the EnableRuntimeDetection block (the actual buggy decision), treating every pod as rule-managed so the container keeps being monitored. The real removal is extracted into removeContainer.
  • containerCallback — ignored containers now call removeContainer directly, so they are always dropped regardless of rule bindings / runtime detection.

Stop wasted rule-binding work when bindings are ignored

  • resourcesToWatch no longer watches RuntimeAlertRuleBinding objects (RBCache is the sole watcher), which also removes the misleading RBCache - refreshed rule bindings rules log.
  • RefreshRuleBindingsRules skips the per-binding rule rebuild but still notifies consumers, so RuleManager keeps recompiling the profile projection spec on Rule CRD changes.
  • RBCache handlers skip pod→binding bookkeeping (addPod/deletePod) since it is never read in this mode.

Tests

  • TestUnregisterContainer: new case keeping a container in ignoreRuleBindings mode with no matching binding (fails on main, passes here).
  • TestResourcesToWatch: new case asserting rule bindings are not watched when ignored.
  • go build ./... clean; pkg/rulebindingmanager/..., pkg/rulemanager, pkg/containerwatcher/v2 green.

Docs

  • docs/CONFIGURATION.md — expanded the ignoreRuleBindings entry to describe the now-explicit behavior.

Follow-up (not in this PR)

Component-test blind spot: the test chart ships an unconditional all-rules-all-pods binding, so ruleManagedPods is always populated and the bug is masked. A component-test variant with ignoreRuleBindings=true and no binding would close the gap.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@matthyx, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 52 minutes and 56 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses rolling per-developer review limits. Reviews become available again as older review attempts age out of the rolling limit window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 140ba0aa-3045-4ab9-8365-b4beccf91634

📥 Commits

Reviewing files that changed from the base of the PR and between d643bad and 3713b72.

📒 Files selected for processing (6)
  • docs/CONFIGURATION.md
  • pkg/containerwatcher/v2/container_watcher_private_test.go
  • pkg/containerwatcher/v2/containercallback.go
  • pkg/rulebindingmanager/cache/cache.go
  • pkg/rulebindingmanager/cache/helpers.go
  • pkg/rulebindingmanager/cache/helpers_test.go
📝 Walkthrough

Walkthrough

In unregisterContainer, a new early return is added at the top of the function: when cw.cfg.IgnoreRuleBindings is enabled, the function immediately returns without proceeding to container removal or shared container data deletion.

Changes

Skip unregistration when IgnoreRuleBindings is enabled

Layer / File(s) Summary
Early return on IgnoreRuleBindings
pkg/containerwatcher/v2/containercallback.go
unregisterContainer gains a three-line guard at the top: if cw.cfg.IgnoreRuleBindings is set, the function returns immediately, bypassing container removal and shared data deletion.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~2 minutes

Poem

A rabbit checks the flag before it hops,
If IgnoreRuleBindings is set, it stops.
No removal, no deletion, just a quick retreat,
Three lines of code make the logic complete.
🐇 Early returns keep the warren neat!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: preventing container unregistration when IgnoreRuleBindings is enabled, which matches the core modification in the code.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ignore-rule-bindings-unregister

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/containerwatcher/v2/containercallback.go`:
- Around line 190-192: Add test cases to the existing test suite in
`container_watcher_private_test.go` that specifically cover the new early return
behavior in the `unregisterContainer` method when `IgnoreRuleBindings`
configuration is true. Create test scenarios that verify the early return
prevents container removal, confirm the container remains in the
`containerCollection`, and ensure shared container data is not deleted when this
configuration flag is enabled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2aa352c2-2fa6-4bda-a619-fb75c43fb189

📥 Commits

Reviewing files that changed from the base of the PR and between 5bda701 and d643bad.

📒 Files selected for processing (1)
  • pkg/containerwatcher/v2/containercallback.go

Comment thread pkg/containerwatcher/v2/containercallback.go Outdated
@github-actions

Copy link
Copy Markdown

Performance Benchmark Results

Node-Agent Resource Usage
Metric BEFORE AFTER Delta
Avg CPU (cores) 0.210 0.221 +5.2%
Peak CPU (cores) 0.221 0.239 +8.4%
Avg Memory (MiB) 353.463 273.909 -22.5%
Peak Memory (MiB) 356.227 283.309 -20.5%
Dedup Effectiveness

No data available.

@slashben slashben left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Confirmed this fixes the bug. Root cause: in IgnoreRuleBindings mode ruleManagedPods is never populated (it's only fed from binding notifications), so when a container's ApplicationProfile reaches end-of-life the EOL-driven unregisterContainer tears down monitoring — detection silently stops after learning completes. Returning early when the flag is set keeps the container monitored. On the production config (EnableRuntimeDetection=true) this behaves the same as a guard placed inside the runtime-detection branch.

A few comments:

1. (Should-fix) Add a regression test. This is a silent-failure bug — detection just stops, no error — so it's exactly the kind that regresses unnoticed. The existing TestUnregisterContainer table covers it with one case (add an ignoreRuleBindings field to the table struct + wire it into the config.Config{...}):

{
    name:                    "Keep container in ignoreRuleBindings mode with no matching binding",
    unregisterContainer:     "container1",
    unregisterContainersPod: "pod1",
    podToContainers:         map[string][]string{"pod1": {"container1"}, "pod2": {"container2"}},
    preRuleManagedPods:      []string{},                           // empty: no bindings
    expectedContainers:      []string{"container1", "container2"}, // both kept
    enableRuntimeDetection:  true,
    ruleBindingsInitialized: true,
    ignoreRuleBindings:      true,
},

Fails on main, passes with this fix.

2. (Nit) Add a one-line comment. A bare if cw.cfg.IgnoreRuleBindings { return } doesn't tell the next reader why — one sentence ("in this mode every pod is evaluated against all rules, so ruleManagedPods stays empty and a container must never be unregistered for lacking a binding") would help.

3. (Question, not a blocker) Effect on the excluded-namespace path. unregisterContainer is also called from the IgnoreContainer branch in containercallback.go (the excludeNamespaces/labels cleanup for kube-system, kubescape, the agent itself). With this early-return, in IgnoreRuleBindings mode those ignored containers are no longer explicitly removed from the container collection. Likely benign — the monitoring callbacks are already skipped for ignored containers — but since armo mode runs with IgnoreRuleBindings=true in production, worth confirming excluded namespaces don't start lingering/monitored. If there's any doubt, scoping the guard inside the if cw.cfg.EnableRuntimeDetection block confines it to the actual buggy decision.

Net: LGTM with the test added; #2/#3 are polish.

Validated end-to-end on a live cluster (chart sets IGNORERULEBINDINGS=true; with no binding, attack-suite containers were unregistered at profile-EOL and a command-injection produced zero detections; restoring monitoring brought the alerts back).

Comment thread pkg/containerwatcher/v2/containercallback.go Outdated
Comment thread pkg/containerwatcher/v2/containercallback.go Outdated
@slashben

Copy link
Copy Markdown
Contributor

Why CI didn't catch this (component-test blind spot)

Test_18_LearningMode actually exercises the exact failure window — it execs wget open.stealth.si after the ApplicationProfile completes and asserts the alert fires (tests/node-agent/component_test.go:1632). So the gap isn't missing coverage of the scenario; it's the test chart config.

The component-test chart sets two things together:

  • tests/node-agent/chart/templates/node-agent/configmap.yaml:40"ignoreRuleBindings": true (same as prod)
  • tests/node-agent/chart/templates/node-agent/default-rule-binding.yaml → an unconditional all-rules-all-pods RuntimeRuleAlertBinding (no installDefault gate — always rendered)

This bug only triggers with ignoreRuleBindings=true AND no matching binding (empty ruleManagedPods). Because the test always ships the binding, every pod lands in ruleManagedPods and unregisterContainer returns at the first guard (ruleManagedPods.Contains) — the buggy "bindings initialized + pod not managed" branch is never reached. So Test 18 stays green.

ignoreRuleBindings default binding ruleManagedPods result
Component-test chart true always created populated bug masked → green
Prod armosec chart (default) true gated behind alertCRD.installDefault=false → none empty bug triggers

CI runs the one config where the binding hides the bug; the shipped chart default runs the one config where it bites.

Suggested coverage: a component-test variant with ignoreRuleBindings=true and no RuntimeRuleAlertBinding, asserting a pod still alerts after its profile completes. (The unit-test case suggested above covers it at the unregisterContainer level; this would close the component-level gap.)

@matthyx
matthyx force-pushed the fix/ignore-rule-bindings-unregister branch from d643bad to 0386943 Compare June 23, 2026 12:46
@matthyx matthyx changed the title fix: prevent container unregistration when IgnoreRuleBindings is true fix: correct IgnoreRuleBindings handling for container monitoring Jun 23, 2026
@matthyx

matthyx commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @slashben — addressed your review:

#3 (excluded-namespace path / scope concern): Confirmed real, and fixed exactly as you suggested. The IgnoreRuleBindings early-return is now scoped inside the EnableRuntimeDetection block, so it only governs the actual buggy decision. The IgnoreContainer (excludeNamespaces/labels) cleanup path no longer routes through that logic at all — it calls a new removeContainer helper directly, so ignored containers are always dropped regardless of IgnoreRuleBindings. No lingering/monitored excluded namespaces in armo mode.

#1/#2 (regression test + comment): done (replied inline).

While in here I also stopped the now-dead rule-binding work in this mode: RBCache no longer watches RuntimeAlertRuleBinding (removing the misleading refreshed rule bindings rules log), RefreshRuleBindingsRules skips the per-binding rebuild but still notifies so RuleManager keeps recompiling the projection spec, and pod→binding bookkeeping is skipped.

CI blind spot: great catch — left as a noted follow-up in the PR description (component-test variant with ignoreRuleBindings=true and no binding). Happy to add it here or in a separate PR, your call.

The previous fix short-circuited the entire unregisterContainer when
IgnoreRuleBindings was true, which also disabled removal of explicitly
ignored containers and cleanup of learning-complete containers when
runtime detection is off.

Scope the behavior correctly:
- unregisterContainer: only treat every pod as rule-managed (keep
  monitoring) inside the runtime-detection block when IgnoreRuleBindings
  is set; extract the actual removal into removeContainer.
- containerCallback: ignored containers now call removeContainer
  directly so they are always dropped regardless of rule bindings.

Also stop the wasted rule-binding work when bindings are ignored:
- Do not watch RuntimeAlertRuleBinding objects (RBCache is the sole
  watcher), removing the misleading "refreshed rule bindings rules" log.
- RefreshRuleBindingsRules skips the per-binding rebuild but still
  notifies consumers so RuleManager keeps recompiling the projection
  spec on rule changes.
- Skip pod->binding bookkeeping in the RBCache handlers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
@matthyx
matthyx force-pushed the fix/ignore-rule-bindings-unregister branch from 0386943 to 3713b72 Compare June 23, 2026 12:52
@github-actions

Copy link
Copy Markdown

Performance Benchmark Results

Node-Agent Resource Usage
Metric BEFORE AFTER Delta
Avg CPU (cores) 0.000 0.000 N/A
Peak CPU (cores) 0.000 0.000 N/A
Avg Memory (MiB) 0.000 0.000 N/A
Peak Memory (MiB) 0.000 0.000 N/A
Dedup Effectiveness

No data available.

@matthyx matthyx added the release Create release label Jun 23, 2026
@github-actions

Copy link
Copy Markdown

Performance Benchmark Results

Node-Agent Resource Usage
Metric BEFORE AFTER Delta
Avg CPU (cores) 0.246 0.229 -6.9%
Peak CPU (cores) 0.259 0.241 -6.7%
Avg Memory (MiB) 330.203 267.919 -18.9%
Peak Memory (MiB) 333.047 271.172 -18.6%
Dedup Effectiveness

No data available.

@matthyx
matthyx merged commit ec6ebae into main Jun 23, 2026
28 checks passed
@matthyx
matthyx deleted the fix/ignore-rule-bindings-unregister branch June 23, 2026 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release Create release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants