Skip to content

chore: deprecate old profiletypes in favor of containerprofiles - #864

Open
entlein wants to merge 32 commits into
kubescape:mainfrom
k8sstormcenter:migrate/sbob
Open

chore: deprecate old profiletypes in favor of containerprofiles#864
entlein wants to merge 32 commits into
kubescape:mainfrom
k8sstormcenter:migrate/sbob

Conversation

@entlein

@entlein entlein commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Overview

migrating old CRD structure to container profile, migrating also all tests

Additional Information

See the issue
Also see new documentation under kubescape.io

Should we keep the migration support of the old way or just completely switch instantly?

Discussion with self -> fully get rid of all AP/NN/ug- the respective CRDs, and clean this all up.

Why? I personally think, it is too confusing to have too many profile types and labels and what-not.
I understand that this set of PRs might not get merged for a long time/ if at all, if there are legacy consumers of the overlays

TODO

  • storage DONE
  • documentation : PR is open retesting ...
  • charts DONE , needs an edit of the images once they are built on kubescape quai
  • remove/rewrite Tests 12/13
  • backend
  • synchronizer
  • test coverage
  • Test09 rca : its not caused by this PR, Test 16 /11 sometimes flaky: not caused by this PR

Multi-container binding (recorded decision)

A ContainerProfile is per-container, but the user-defined-profile binding is a pod label. Go-forward contract:

  • A user authors one ContainerProfile per container, named <label-value>-<containerName>, where <label-value> is the pod label kubescape.io/user-defined-profile. The read path binds each container to its own ContainerProfile by that name, falling back to the bare <label-value> only for single-container pods.
  • This mirrors the per-container granularity of learned profiles (one CP per container). No CRD schema change — the binding is a read-path naming convention, not a spec field. The alternative (a container-selector field in the spec) was considered and rejected as heavier for no functional gain.
  • Storage stores authored ContainerProfiles verbatim by name and derives a per-CP GeneratedNetworkPolicy from each CP's container metadata; containers are handled independently (no cross-container merge).

Example — pod label kubescape.io/user-defined-profile: mc35 on a pod with containers app + sidecar → ContainerProfiles mc35-app and mc35-sidecar; a single-container pod → bare mc35.

Tested: node-agent Test_35_MultiContainerPerContainerBinding (per-container R0001 isolation, no cross-inheritance) and a storage per-container GeneratedNetworkPolicy test.

Summary by CodeRabbit

  • New Features

    • Added per-container, user-authored ContainerProfiles selected through pod labels.
    • Added profile-name fallback, retry handling, and unresolved-profile metrics.
    • Updated security checks to use ContainerProfile-based process, file, syscall, capability, and network data.
  • Bug Fixes

    • Authored profiles now take precedence over learned data and persist through transient refresh errors.
    • Learned profiles are no longer treated as authored profiles.
  • Tests

    • Expanded coverage for profile binding, refresh behavior, wildcard matching, projections, and network validation.

Entlein and others added 11 commits July 25, 2026 14:05
… -> ContainerProfile

ConvertUserProfilesToContainerProfile produces the single user-defined
ContainerProfile equivalent to a legacy user-authored ApplicationProfile +
NetworkNeighborhood pair, reusing the existing projectUserProfiles merge onto
an empty base.

The differential oracle pins the migration contract at the enforcement level:
for representative user-defined shapes (opens/exec argv wildcards, HTTP
endpoints, egress/ingress + LabelSelector) the ProjectedContainerProfile from
the legacy AP+NN overlay path equals the one from using the converted
ContainerProfile as the base with no overlay. Behaviour-preserving by
construction.

Signed-off-by: entlein <einentlein@gmail.com>
…e authoritative base

When the user-defined-profile pod label names a ContainerProfile carrying
managed-by: User, the cache now uses it directly as the container's base
profile (the migrated "new way"), instead of overlaying a legacy
ApplicationProfile + NetworkNeighborhood pair. It falls back to the legacy AP+NN
pair when no such CP exists, which still fires the existing deprecation signal.

- add-time (tryPopulateEntry) and refresh (reconciler) both prefer the user CP,
  gated on the managed-by: User annotation so a learned CP at the same name is
  never mistaken for a user-defined one
- UserCPRef/UserCPRV bookkeeping mirrors the legacy UserAPRV/UserNNRV RV tracking
  so the reconciler re-fetches and rebuilds only when the user CP changes

Signed-off-by: entlein <einentlein@gmail.com>
Test_28 now creates one ContainerProfile (managed-by: User) carrying the merged
exec/syscall + egress/selector surfaces, instead of a separate user-authored
ApplicationProfile + NetworkNeighborhood pair — exercising the migrated
read-path end to end. Assertions unchanged.

Signed-off-by: entlein <einentlein@gmail.com>
…verlay

The new path (converted CP as base, no overlay) is ~35% faster and allocates
~27% less than the legacy AP+NN overlay per projection, since it skips the
two-object merge.

Signed-off-by: entlein <einentlein@gmail.com>
Test_27 (opens R0002, both the regex and the curl-wildcard profile sites),
Test_32 (R0040 argv wildcards), and Test_33 (opens wildcard anchoring — the
previously-fixed one) now each create a single user-defined ContainerProfile
(managed-by: User) carrying the merged exec/open/syscall (+ egress/selector for
32) surfaces, replacing the legacy ApplicationProfile + NetworkNeighborhood
pair. Assertions unchanged. Test_28 was ported in an earlier commit.

Signed-off-by: entlein <einentlein@gmail.com>
containsDynamicSegment recognised only the one-segment DynamicIdentifier
('⋯'), so a path-surface opens entry bearing the zero-or-more
WildcardIdentifier ('*') — e.g. '/etc/ssl/*' — was routed to Values as if it
were a literal. was_path_opened tolerates this (Values and Patterns are both
matched via CompareDynamic), but it is wrong for any consumer that treats
Values as exact membership, and it drops '*'-only entries a rule needs when
spec.All is false and no prefix/suffix matcher retains them. Recognise both
wildcard markers. Regression test pins '/etc/ssl/*' -> Patterns.

Pre-existing in upstream (identical containsDynamicSegment).

Signed-off-by: Entlein <eineintlein@gmail.com>
Signed-off-by: entlein <einentlein@gmail.com>
Test_33's anchoring subtests assert R0002 alerts, but the rule's file-access
monitoring is opt-in (monitored prefixes incl. /etc/). Test_33 never applied
r0002-files-access-enabled.yaml (Test_27 does via enableR0002ForTest), so R0002
never evaluated the opens and every 'expect alert' case silently passed as a
no-alert — invisible because Test_33 had never run in CI. Enable it like Test_27.
Verified on a live cluster: /etc/ssl/* correctly alerts on the bare parent
/etc/ssl and stays silent on the child /etc/ssl/openssl.cnf.

Signed-off-by: entlein <einentlein@gmail.com>
…tations

A user-authored profile is authoritative and complete by definition — it should
not carry the learning-lifecycle status/completion markers, nor a managed-by
annotation. The pod's user-defined-profile label is what declares it
user-authored (a signature, added by the signing tooling, is the integrity
marker).

- read-path/reconciler no longer gate on managed-by: the label-referenced CP is
  used as authoritative, and the entry State is forced to Completed+Full so the
  rule engine enforces it despite the absent completion annotation
- the converter emits clean CPs (name + namespace + spec only)
- fake client models an absent overlay-name fetch (drives the legacy fallback)

Signed-off-by: entlein <einentlein@gmail.com>
…mple

- Test_28 now loads its user-defined ContainerProfile from a real yaml
  (resources/containerprofile-user-defined-network.yaml) via a loader helper, so
  the fixture doubles as the copy-pasteable "how to author a user-defined
  profile" example.
- The example — and all the test CPs — carry only name + spec: the nonsensical
  learning-lifecycle annotations (status/completion) and the managed-by marker
  are dropped (node-agent now treats a label-referenced CP as authoritative and
  forces the enforce-state itself).

Signed-off-by: entlein <einentlein@gmail.com>
Migrate the network-wildcards NetworkNeighborhood fixtures into their
user-authored ContainerProfile form so users can copy-paste them to
author user-defined-profile allow-lists:

- per-container spec.egress/ingress (NN's spec.containers[] collapses,
  one CP document per container; fixture 20 splits into two)
- no lifecycle annotations — name only (namespace injected by tooling),
  matching the clean user-managed CP contract
- teaching comments preserved verbatim from the NN fixtures

Adds 00-fusioncore-homoglyph-attack.yaml: a pinned single-vendor
allow-list and the look-alike (homoglyph) domains its exact-match
dnsNames compare rejects (each fires R0005).

All 21 documents strict-parse against v1beta1 ContainerProfile and
carry zero annotations (verified).

Signed-off-by: entlein <einentlein@gmail.com>
The component tests build their user-defined profiles inline as
ContainerProfiles (Test_27/32/33) or load a CP yaml (Test_28); the
legacy ApplicationProfile / NetworkNeighborhood fixtures they were
derived from are no longer referenced by any Go test.

Migrate the two CT-relevant ones to their user-authored CP form and
delete the orphan:

- exec-arg-wildcards-profile.yaml (AP curl-32-overlay) ->
  containerprofile-exec-arg-wildcards.yaml. CP form mirrors Test_32's
  inline CP exactly (same execs incl. busybox-symlink + literal-* entries,
  same syscalls, matchLabels app: curl-32). Demonstrates the exec-arg
  wildcard grammar for authoring.
- known-network-neighborhood.yaml (NN fusioncore-network) ->
  containerprofile-fusioncore-network.yaml. Clean user-managed CP: name
  only, no managed-by / status / completion annotations.
- user-profile.yaml: deleted. Zero references anywhere; its nginx/server
  exec surface matches no current test or deployment.

Both new documents strict-parse against v1beta1 ContainerProfile and
carry zero annotations (verified). CT compiles unchanged (go vet -tags
component); no Go test referenced the deleted files.

Signed-off-by: entlein <einentlein@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR migrates profile handling from legacy ApplicationProfile and NetworkNeighborhood overlays to authored ContainerProfiles. It updates cache refresh, CEL namespaces, storage and watcher APIs, metrics, component tests, and fixtures.

Changes

ContainerProfile cache and refresh

Layer / File(s) Summary
Authored overlay population and refresh
pkg/objectcache/containerprofilecache/*
Label-referenced ContainerProfiles are resolved per container, tracked through UserCPRef and UserCPRV, and used as the authoritative projection source.
Projection regression coverage
pkg/objectcache/containerprofilecache/projection_golden_test.go, pkg/objectcache/containerprofilecache/testdata/golden/*
Golden, idempotence, and spec-hash tests cover projected ContainerProfile output.

CEL and integration migration

Layer / File(s) Summary
ContainerProfile CEL APIs
pkg/rulemanager/cel/*
CEL bindings move from ap.* and nn.* to cp.* and cpnetwork.*, backed by ContainerProfiles.
CEL behavior tests
pkg/rulemanager/cel/libraries/containerprofile*/**
Tests cover caching, matching, wildcard behavior, pod-spec exemptions, endpoint handling, and unavailable profiles.

Platform and test migration

Layer / File(s) Summary
Storage, metrics, mocks, and watchers
pkg/storage/*, pkg/metricsmanager/*, pkg/objectcache/v1/mock.go, pkg/watcher/dynamicwatcher/*
Legacy AP/NN accessors and watcher paths are removed. ContainerProfile metrics and test surfaces are added.
Component tests and resources
tests/component_test.go, tests/testutils/k8s.go, tests/resources/*, tests/chart/templates/*
Component helpers, readiness checks, authored-profile scenarios, network fixtures, and default rules use ContainerProfiles and cp.* expressions.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: matthyx, slashben

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: replacing legacy profile types with ContainerProfiles.
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.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

entlein added 2 commits July 25, 2026 14:23
Signed-off-by: entlein <einentlein@gmail.com>
Signed-off-by: entlein <einentlein@gmail.com>
@entlein
entlein marked this pull request as ready for review July 26, 2026 17:55

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
pkg/objectcache/containerprofilecache/containerprofilecache.go (1)

380-458: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Non-terminal consolidated cp permanently blocks an already-available authoritative user-defined CP.

The terminal-status gate on the consolidated cp (Lines 380-391) runs before userDefinedCP is fetched (Lines 402-444) and before it replaces cp (Lines 455-457). If a pod already has a non-terminal (still-learning) consolidated CP at the moment the user-defined-profile label is added — the exact scenario this PR's migration enables for already-running workloads — tryPopulateEntry returns false forever, even though a ready, authoritative userDefinedCP exists. The pending-retry loop re-runs this same function on every tick, so it hits the same gate every time and never gets past it to check userDefinedCP.

Fetch the overlay first and skip the gate once it resolves an authoritative profile:

🐛 Proposed fix
-	// Only cache profiles whose status is terminal (Completed or TooLarge).
-	// Learning/ready profiles are still being written; caching them would let
-	// rules fire against incomplete data. TooLarge is terminal: the manager
-	// stopped collecting but the truncated data is still valid for detection.
-	// Return false so the synthetic-CP fallback below does not bypass the gate.
-	if cp != nil && !isTerminalCPStatus(cp.Annotations[helpersv1.StatusMetadataKey]) {
-		logger.L().Debug("tryPopulateEntry: CP status not terminal; keeping pending",
-			helpers.String("containerID", containerID),
-			helpers.String("namespace", ns),
-			helpers.String("status", cp.Annotations[helpersv1.StatusMetadataKey]))
-		return false
-	}
-
 	// Fetch user-authored legacy CRDs when the pod carries the
-	// UserDefinedProfileMetadataKey label. ...
+	// UserDefinedProfileMetadataKey label. Fetched before the terminal-status
+	// gate below so a non-terminal (still-learning) consolidated CP never
+	// blocks an already-available authoritative user-defined CP.
 	var userAP *v1beta1.ApplicationProfile
 	var userNN *v1beta1.NetworkNeighborhood
 	var userDefinedCP *v1beta1.ContainerProfile
 	overlayName, hasOverlay := container.K8s.PodLabels[helpersv1.UserDefinedProfileMetadataKey]
 	if hasOverlay && overlayName != "" {
 		... (unchanged fetch logic)
 	}
+
+	// Only cache profiles whose status is terminal (Completed or TooLarge),
+	// unless an authoritative user-defined CP is already available.
+	if cp != nil && userDefinedCP == nil && !isTerminalCPStatus(cp.Annotations[helpersv1.StatusMetadataKey]) {
+		logger.L().Debug("tryPopulateEntry: CP status not terminal; keeping pending",
+			helpers.String("containerID", containerID),
+			helpers.String("namespace", ns),
+			helpers.String("status", cp.Annotations[helpersv1.StatusMetadataKey]))
+		return false
+	}

The same ordering exists in refreshOneEntry (reconciler.go Lines 301-332 run the terminal-check before the userDefinedCP re-fetch at Lines 406-427); worth the same fix there once addressed here.

🤖 Prompt for 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.

In `@pkg/objectcache/containerprofilecache/containerprofilecache.go` around lines
380 - 458, Reorder the user-defined profile lookup in tryPopulateEntry so
userDefinedCP is fetched before the consolidated cp terminal-status gate. Allow
processing to continue when an authoritative userDefinedCP is available, while
retaining the existing gate for non-terminal cp without one. Apply the same
ordering and bypass behavior in refreshOneEntry, preserving legacy fallback
handling.
tests/component_test.go (1)

2250-2261: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Remove unneeded metadata labels from the user-defined container profile.

ApiGroupMetadataKey, ApiVersionMetadataKey, and Related* metadata labels are not consulted when selecting/projecting a user-defined ContainerProfile; the cache uses kubescape.io/user-defined-profile. Strip these labels on the inline CP so the fixture matches the documented “name + spec” contract.

🤖 Prompt for 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.

In `@tests/component_test.go` around lines 2250 - 2261, Update the inline
ContainerProfile fixture in the component test to remove ApiGroupMetadataKey,
ApiVersionMetadataKey, and all Related* metadata labels, leaving only the
metadata required by the documented name-and-spec contract and the cache’s
kubescape.io/user-defined-profile label if applicable.
🧹 Nitpick comments (5)
pkg/objectcache/containerprofilecache/containerprofilecache_test.go (1)

221-256: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing regression coverage for a failed initial CP fetch.

This test only covers the happy path (successful userCP fetch). Consider adding a case where the initial GetContainerProfile call for the overlay name fails (e.g. cpErr on that name) and a later addContainer/refresh succeeds — this is exactly the scenario where UserCPRef bookkeeping needs to survive a failed first attempt (see the critical issue raised in containerprofilecache.go Lines 402-546).

🤖 Prompt for 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.

In `@pkg/objectcache/containerprofilecache/containerprofilecache_test.go` around
lines 221 - 256, Add regression coverage alongside
TestOverlayPath_UserDefinedCP_NewWay for an initial user-defined
ContainerProfile fetch failure: configure the fake client to return cpErr for
the overlay name, call addContainer, then make the later fetch succeed and
trigger addContainer or refresh. Assert the eventual projection succeeds and
UserCPRef/UserCPRV are preserved, verifying the bookkeeping survives the failed
first attempt.
tests/resources/containerprofile-user-defined-network.yaml (1)

58-68: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Redundant deprecated singular fields (dns, ipAddress) alongside their plural equivalents.

This egress entry sets dns and dnsNames to the same value, and uses singular ipAddress — all legacy/deprecated forms per this PR's own 08-deprecated-ipaddress.yaml fixture. As an authoring example for "the new way," consider keeping only the canonical plural fields to avoid ambiguity for authors copying this example.

📝 Suggested fix
   egress:
   - identifier: fusioncore-egress
     type: external
-    dns: fusioncore.ai.
     dnsNames:
     - fusioncore.ai.
-    ipAddress: 162.0.217.171
+    ipAddresses:
+    - 162.0.217.171
     ports:
🤖 Prompt for 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.

In `@tests/resources/containerprofile-user-defined-network.yaml` around lines 58 -
68, Update the fusioncore-egress entry in the user-defined network fixture to
remove the deprecated singular dns and ipAddress fields, retaining the canonical
dnsNames and existing ports configuration.
tests/component_test.go (1)

1653-1657: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stale doc comment: still describes ApplicationProfile.

The deployWithProfile helper now builds a v1beta1.ContainerProfile (Lines 1661-1679), but the preceding doc comment still says "creates a user-defined ApplicationProfile." Update it to avoid misleading future readers.

📝 Suggested fix
-	// deployWithProfile creates a user-defined ApplicationProfile with the
+	// deployWithProfile creates a user-defined ContainerProfile with the
 	// given Opens list, polls until it is retrievable from storage, then
 	// deploys nginx with the kubescape.io/user-defined-profile label
 	// pointing at it, and waits for the pod to be ready.
🤖 Prompt for 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.

In `@tests/component_test.go` around lines 1653 - 1657, Update the doc comment for
the deployWithProfile helper to describe creation of a user-defined
v1beta1.ContainerProfile instead of an ApplicationProfile, while preserving the
existing description of its deployment and readiness behavior.
tests/resources/containerprofile-fusioncore-network.yaml (1)

25-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Uses deprecated singular ipAddress in a "new way" authoring example.

Elsewhere in this PR (network-wildcards-cp/08-deprecated-ipaddress.yaml, README) the singular ipAddress field is documented as deprecated in favor of plural ipAddresses. Since this fixture's own comment frames it as the "new way" ContainerProfile authoring example, consider using the canonical ipAddresses: ["162.0.217.171"] form for consistency.

📝 Suggested fix
     dnsNames:
     - fusioncore.ai.
-    ipAddress: 162.0.217.171
+    ipAddresses:
+    - 162.0.217.171
     ports:
🤖 Prompt for 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.

In `@tests/resources/containerprofile-fusioncore-network.yaml` around lines 25 -
34, Update the egress entry in the ContainerProfile example to replace the
deprecated singular ipAddress field with the canonical ipAddresses field
containing 162.0.217.171, while preserving the existing network rule and other
fields.
tests/resources/network-wildcards-cp/07-mixed-ip-list.yaml (1)

8-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align the fixture description with the actual matching path.

Because * makes every valid IP match, this fixture cannot exercise list ordering or first-match behavior. Either remove those claims or use a mixed literal/CIDR case without *; keep this fixture focused on accepting mixed forms with the sentinel.

🤖 Prompt for 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.

In `@tests/resources/network-wildcards-cp/07-mixed-ip-list.yaml` around lines 8 -
13, Update the comments in the mixed IP list fixture to remove claims that it
tests list ordering, loop ordering, or first-match short-circuiting, since the
sentinel `*` matches every valid IP. Keep the description focused on accepting
mixed literal/CIDR forms with the unconstrained sentinel.
🤖 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/objectcache/containerprofilecache/containerprofilecache.go`:
- Around line 402-444: Record UserCPRef whenever a non-empty user-defined
profile label is present, even when the initial GetContainerProfile call fails,
so refreshOneEntry can retry it later. In rebuildEntryFromSources, clear
UserAPRef and UserNNRef when userDefinedCP becomes authoritative, preserving the
mutually exclusive reference invariant while retaining legacy refs only when no
user CP is available.

In `@tests/resources/network-wildcards-cp/13-dns-trailing-dot-normalisation.yaml`:
- Around line 5-10: Update the YAML fixture’s profile entries to match its
documented cases: replace the api.github.com entry with api.stripe.com if
testing both trailing-dot forms, and ensure the expectations in both referenced
sections consistently describe the actual profile-side hostname.

---

Outside diff comments:
In `@pkg/objectcache/containerprofilecache/containerprofilecache.go`:
- Around line 380-458: Reorder the user-defined profile lookup in
tryPopulateEntry so userDefinedCP is fetched before the consolidated cp
terminal-status gate. Allow processing to continue when an authoritative
userDefinedCP is available, while retaining the existing gate for non-terminal
cp without one. Apply the same ordering and bypass behavior in refreshOneEntry,
preserving legacy fallback handling.

In `@tests/component_test.go`:
- Around line 2250-2261: Update the inline ContainerProfile fixture in the
component test to remove ApiGroupMetadataKey, ApiVersionMetadataKey, and all
Related* metadata labels, leaving only the metadata required by the documented
name-and-spec contract and the cache’s kubescape.io/user-defined-profile label
if applicable.

---

Nitpick comments:
In `@pkg/objectcache/containerprofilecache/containerprofilecache_test.go`:
- Around line 221-256: Add regression coverage alongside
TestOverlayPath_UserDefinedCP_NewWay for an initial user-defined
ContainerProfile fetch failure: configure the fake client to return cpErr for
the overlay name, call addContainer, then make the later fetch succeed and
trigger addContainer or refresh. Assert the eventual projection succeeds and
UserCPRef/UserCPRV are preserved, verifying the bookkeeping survives the failed
first attempt.

In `@tests/component_test.go`:
- Around line 1653-1657: Update the doc comment for the deployWithProfile helper
to describe creation of a user-defined v1beta1.ContainerProfile instead of an
ApplicationProfile, while preserving the existing description of its deployment
and readiness behavior.

In `@tests/resources/containerprofile-fusioncore-network.yaml`:
- Around line 25-34: Update the egress entry in the ContainerProfile example to
replace the deprecated singular ipAddress field with the canonical ipAddresses
field containing 162.0.217.171, while preserving the existing network rule and
other fields.

In `@tests/resources/containerprofile-user-defined-network.yaml`:
- Around line 58-68: Update the fusioncore-egress entry in the user-defined
network fixture to remove the deprecated singular dns and ipAddress fields,
retaining the canonical dnsNames and existing ports configuration.

In `@tests/resources/network-wildcards-cp/07-mixed-ip-list.yaml`:
- Around line 8-13: Update the comments in the mixed IP list fixture to remove
claims that it tests list ordering, loop ordering, or first-match
short-circuiting, since the sentinel `*` matches every valid IP. Keep the
description focused on accepting mixed literal/CIDR forms with the unconstrained
sentinel.
🪄 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 Plus

Run ID: 4b7c84a9-bef1-4de2-8db4-3ffe8bc86801

📥 Commits

Reviewing files that changed from the base of the PR and between 6a86345 and 83bc9fb.

📒 Files selected for processing (36)
  • pkg/objectcache/containerprofilecache/containerprofilecache.go
  • pkg/objectcache/containerprofilecache/containerprofilecache_test.go
  • pkg/objectcache/containerprofilecache/projection_apply.go
  • pkg/objectcache/containerprofilecache/projection_wildcard_classification_test.go
  • pkg/objectcache/containerprofilecache/reconciler.go
  • pkg/objectcache/containerprofilecache/reconciler_test.go
  • tests/component_test.go
  • tests/resources/containerprofile-exec-arg-wildcards.yaml
  • tests/resources/containerprofile-fusioncore-network.yaml
  • tests/resources/containerprofile-user-defined-network.yaml
  • tests/resources/exec-arg-wildcards-profile.yaml
  • tests/resources/known-network-neighborhood.yaml
  • tests/resources/network-wildcards-cp/00-fusioncore-homoglyph-attack.yaml
  • tests/resources/network-wildcards-cp/01-literal-ipv4.yaml
  • tests/resources/network-wildcards-cp/02-literal-ipv6.yaml
  • tests/resources/network-wildcards-cp/03-cidr-ipv4.yaml
  • tests/resources/network-wildcards-cp/04-cidr-ipv6.yaml
  • tests/resources/network-wildcards-cp/05-any-ip-sentinel.yaml
  • tests/resources/network-wildcards-cp/06-any-as-cidr.yaml
  • tests/resources/network-wildcards-cp/07-mixed-ip-list.yaml
  • tests/resources/network-wildcards-cp/08-deprecated-ipaddress.yaml
  • tests/resources/network-wildcards-cp/09-dns-literal.yaml
  • tests/resources/network-wildcards-cp/10-dns-leading-wildcard.yaml
  • tests/resources/network-wildcards-cp/11-dns-mid-ellipsis.yaml
  • tests/resources/network-wildcards-cp/12-dns-trailing-star.yaml
  • tests/resources/network-wildcards-cp/13-dns-trailing-dot-normalisation.yaml
  • tests/resources/network-wildcards-cp/14-recursive-star-rejected.yaml
  • tests/resources/network-wildcards-cp/15-egress-and-ingress.yaml
  • tests/resources/network-wildcards-cp/16-egress-none.yaml
  • tests/resources/network-wildcards-cp/17-realistic-stripe-api.yaml
  • tests/resources/network-wildcards-cp/18-cluster-dns-via-mid-ellipsis.yaml
  • tests/resources/network-wildcards-cp/19-port-protocol-with-cidr.yaml
  • tests/resources/network-wildcards-cp/20-multi-container-mixed-wildcards.yaml
  • tests/resources/network-wildcards-cp/README.md
  • tests/resources/network-wildcards/README.md
  • tests/resources/user-profile.yaml
💤 Files with no reviewable changes (4)
  • tests/resources/user-profile.yaml
  • tests/resources/exec-arg-wildcards-profile.yaml
  • tests/resources/network-wildcards/README.md
  • tests/resources/known-network-neighborhood.yaml

Comment thread pkg/objectcache/containerprofilecache/containerprofilecache.go
Comment thread tests/resources/network-wildcards-cp/13-dns-trailing-dot-normalisation.yaml Outdated
… decommission them -step 1 of many -- I understand that this has a long tail of decommissions

Signed-off-by: entlein <einentlein@gmail.com>

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
pkg/objectcache/containerprofilecache/reconciler.go (2)

380-388: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle NotFound as deletion rather than a transient error.

Once UserCPRV is set, every fetch error preserves the cached authoritative policy. Deleting the referenced CP therefore leaves its old policy enforced indefinitely instead of rebuilding from the learned CP. Return early only for non-NotFound failures; let NotFound set userDefinedCP to nil so the RV mismatch triggers a rebuild.

Proposed fix
- if userCPErr != nil && e.UserCPRV != "" {
+ if userCPErr != nil && e.UserCPRV != "" && !apierrors.IsNotFound(userCPErr) {
    logger.L().Debug("refreshOneEntry: user-defined CP fetch failed; keeping cached entry",
      helpers.String("containerID", id),
      helpers.String("name", e.UserCPRef.Name),
      helpers.Error(userCPErr))
    return
  }
🤖 Prompt for 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.

In `@pkg/objectcache/containerprofilecache/reconciler.go` around lines 380 - 388,
Update the user-defined CP error handling in refreshOneEntry so cached policy is
preserved only for non-NotFound fetch failures. Detect the NotFound condition
before the early return, allow it to continue with userDefinedCP set to nil, and
retain the existing return behavior for other errors so the RV mismatch triggers
rebuilding.

369-379: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate that the referenced CP is user-managed before adopting it.

The migration contract identifies this source as managed-by: User, but this path accepts any same-name CP. A learned CP can bypass the terminal-status gate above and then be forced to Completed/Full in the rebuilt entry. Reject or ignore profiles without ManagedByMetadataKey == ManagedByUserValue, with coverage for an invalid reference.

🤖 Prompt for 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.

In `@pkg/objectcache/containerprofilecache/reconciler.go` around lines 369 - 379,
Validate the ContainerProfile fetched in the UserCPRef refresh within the
reconciler before adopting it. Only accept profiles whose ManagedByMetadataKey
equals ManagedByUserValue; ignore or reject invalid references so learned
profiles cannot bypass terminal-status handling. Add coverage for a referenced
profile with invalid management metadata, while preserving transient fetch-error
behavior.
🧹 Nitpick comments (1)
pkg/objectcache/containerprofilecache/containerprofilecache_test.go (1)

223-226: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that the user CP is the effective policy.

These assertions only prove it was fetched and recorded. A regression could retain the learned/synthetic CP while copying UserCPRef/RV and still pass. Assert a user-CP-specific projected value, or at minimum entry.State.Name == "override".

🤖 Prompt for 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.

In `@pkg/objectcache/containerprofilecache/containerprofilecache_test.go` around
lines 223 - 226, Strengthen the assertions in the user-defined CP test to verify
the user CP is the effective policy, not merely recorded for refresh. After the
existing UserCPRef checks, assert the projected profile contains a
user-CP-specific value or, at minimum, that entry.State.Name equals "override";
keep the existing fetch and reference assertions unchanged.
🤖 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 `@tests/resources/nnlint_test.go`:
- Around line 176-211: Update lintEndpoint to apply the existing dnsNameProblem
validation to the singular e.DNS value and validIPEntry validation to the
singular e.IPAddress value, in addition to the plural DNSNames and IPAddresses
loops. Emit the same R-NN-13 and R-NN-15 rules with contextual messages so
malformed deprecated fields are reported.

---

Outside diff comments:
In `@pkg/objectcache/containerprofilecache/reconciler.go`:
- Around line 380-388: Update the user-defined CP error handling in
refreshOneEntry so cached policy is preserved only for non-NotFound fetch
failures. Detect the NotFound condition before the early return, allow it to
continue with userDefinedCP set to nil, and retain the existing return behavior
for other errors so the RV mismatch triggers rebuilding.
- Around line 369-379: Validate the ContainerProfile fetched in the UserCPRef
refresh within the reconciler before adopting it. Only accept profiles whose
ManagedByMetadataKey equals ManagedByUserValue; ignore or reject invalid
references so learned profiles cannot bypass terminal-status handling. Add
coverage for a referenced profile with invalid management metadata, while
preserving transient fetch-error behavior.

---

Nitpick comments:
In `@pkg/objectcache/containerprofilecache/containerprofilecache_test.go`:
- Around line 223-226: Strengthen the assertions in the user-defined CP test to
verify the user CP is the effective policy, not merely recorded for refresh.
After the existing UserCPRef checks, assert the projected profile contains a
user-CP-specific value or, at minimum, that entry.State.Name equals "override";
keep the existing fetch and reference assertions unchanged.
🪄 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 Plus

Run ID: 394d9bb1-b653-454c-8c2e-9dd9ea0f41d9

📥 Commits

Reviewing files that changed from the base of the PR and between 83bc9fb and fd5ed04.

📒 Files selected for processing (8)
  • pkg/objectcache/containerprofilecache/containerprofilecache.go
  • pkg/objectcache/containerprofilecache/containerprofilecache_test.go
  • pkg/objectcache/containerprofilecache/export_test.go
  • pkg/objectcache/containerprofilecache/reconciler.go
  • pkg/objectcache/containerprofilecache/reconciler_test.go
  • pkg/objectcache/containerprofilecache/t8_overlay_refresh_test.go
  • tests/resources/network-wildcards-cp/13-dns-trailing-dot-normalisation.yaml
  • tests/resources/nnlint_test.go
💤 Files with no reviewable changes (3)
  • pkg/objectcache/containerprofilecache/t8_overlay_refresh_test.go
  • pkg/objectcache/containerprofilecache/export_test.go
  • pkg/objectcache/containerprofilecache/reconciler_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/resources/network-wildcards-cp/13-dns-trailing-dot-normalisation.yaml
  • pkg/objectcache/containerprofilecache/containerprofilecache.go

Comment thread tests/resources/nnlint_test.go
// the container stays pending; UserCPRef — recorded unconditionally below —
// drives the reconciler to retry the CP on every tick until it materialises.
var userDefinedCP *v1beta1.ContainerProfile
overlayName, hasOverlay := container.K8s.PodLabels[helpersv1.UserDefinedProfileMetadataKey]

@matthyx matthyx Jul 28, 2026

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.

Every container in the pod resolves the same overlayName and adopts the same CP — the binding is a pod label, but a ContainerProfile is per-container. A sidecar inherits the app container's allow-list and vice versa.

The legacy path handled this: projectUserProfiles(..., container.Runtime.ContainerName) selected the matching spec.containers[] entry across containers / initContainers / ephemeralContainers. Nothing replaces it.

We need either a naming convention the read path derives (<label-value>-<containerName>, falling back to the bare name for single-container pods) or a container selector inside the CP spec.

Worth settling before the storage PR locks the shape in — tests/resources/network-wildcards-cp/README.md already documents "one CP document per container", and fixture 20 emits nw-20-multi-container-frontend + nw-20-multi-container-sidecar, but nothing can currently bind either one to a specific container.

// test is only that iteration stopped early.
}

// TestRefreshFastSkipWhenAllRVsMatch — delta #4. When CP RV and both overlay

@matthyx matthyx Jul 28, 2026

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.

We should migrate these tests to CPs rather than drop them. This hunk and the ones below remove every refresh-path overlay test (−274 here, −114 in t8_overlay_refresh_test.go), and nothing replaces them for the user-CP path — TestOverlayPath_UserDefinedCP_NewWay only covers the add path. That's why the entry.RV bug is green in CI.

The four worth restoring in CP form:

  • fast-skip when the CP RV and UserCPRV both match (TestRefreshFastSkipWhenAllRVsMatch)
  • rebuild when only UserCPRV changes (TestRefreshRebuildsOnUserAPChange)
  • end-to-end: edit the authored CP in the fake, one refreshAllEntries, assert the projection reflects the edit (TestT8_EndToEndRefreshUpdatesProjection) — this is the one that catches the freeze
  • user-defined CP present with no learned CP (TestUserDefinedProfileOnly_NoBaseCP)

countingProfileClient.GetContainerProfile (:78) also ignores its name argument, so it can't distinguish base from authored CP — it needs the same name-matching treatment fakeProfileClient just got. And countingMetrics.legacyLoad (:129) is now unused after these deletions; unused/staticcheck will flag it.

}
if entry.UserNNRef == nil {
entry.UserNNRef = &namespacedName{Namespace: ns, Name: overlayName}
entry.UserCPRef = &namespacedName{Namespace: ns, Name: overlayName}

@matthyx matthyx Jul 28, 2026

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.

This forces Completed/Full without ever checking that the fetched CP is user-authored, or that it's finished. The isTerminalCPStatus gate at :375 — "Learning/ready profiles are still being written; caching them would let rules fire against incomplete data" — only guards the base CP fetched by the derived slug at :319. The label-referenced CP fetched at :391-407 gets no equivalent check, becomes the profile at :419-421, and has its real annotations overwritten here.

Tested on this branch: a CP carrying status: ready / completion: partial published at the label's name is cached as Status="completed" Completion="complete". Result is false positives — rules alert on everything legitimate that wasn't learned yet.

3f9299b's gate prevented exactly this; f2f730f removed it on the grounds that a signature is the real integrity marker, but "check signature logic" is still an open TODO, so nothing verifies anything today.

Suggest gating on the negative instead — a learned CP wears lifecycle annotations, an authored one doesn't. This belongs right after the fetch (~:407) or inside the if userDefinedCP != nil below, not at this line, where userDefinedCP may be nil:

if userDefinedCP != nil {
	if _, learned := userDefinedCP.Annotations[helpersv1.StatusMetadataKey]; learned {
		logger.L().Warning("user-defined-profile label resolves to a learned ContainerProfile; ignoring", ...)
		userDefinedCP = nil
	}
}

That makes "authored profiles carry no annotations" self-enforcing without blocking on the signature work.

@matthyx matthyx 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.

Reviewed locally against main at fd5ed04. The direction is right — one unified user-authored CRD instead of the AP+NN pair is a real simplification, and the commit messages are unusually good at stating the contract each change pins. Two things block, and a couple of design questions are worth settling before this leaves draft.

Blockers

Authored ContainerProfile edits are never picked up. entry.RV ends up holding the authored CP's ResourceVersion, while entry.CPName points at the learned slug — which never exists, because learning is suppressed for user-defined containers. The permanent 404 then reads as a transient error and the entry is frozen for the container's lifetime. I verified this and the two-hunk fix is inline (:419, :466); after it, a refresh test goes red→green and the rest of the package is unchanged. It also fixes the learned+authored case, where the authored RV currently shadows the learned one.

Multi-container pods lose per-container separation. The binding is a pod label but a ContainerProfile is per-container, so every container in the pod adopts the same profile. The old path selected the right spec.containers[] entry by container name; nothing replaces it. Worth noting the PR's own network-wildcards-cp/README.md documents "one CP document per container", which the read path can't currently express — that gap should be closed before the storage PR locks the shape in.

Worth settling

  • Nothing validates that the label-referenced CP is user-authored, and its state is force-set to Completed/Full. A profile with status: ready / completion: partial at that name gets enforced as complete — I tested it. 3f9299b's gate prevented exactly this; f2f730f removed it on the grounds that a signature is the real integrity marker, but "check signature logic" is still open on your TODO list, so right now nothing verifies anything. Suggestion inline.
  • The upgrade path fails silently. A workload still on user-defined AP+NN gets no profile and no warning — just a Debug line and a container pending forever. A Warning + metric would make it visible; keeping the AP/NN read behind a deprecation flag for one release would make it safe. Your call which, but "couldn't find any active consumers" is a bet I'd rather not take silently on a security control.
  • The refresh-path tests were removed where the regressions landed (−274 in reconciler_test.go, −114 in t8_overlay_refresh_test.go), with nothing covering the user-CP path — TestOverlayPath_UserDefinedCP_NewWay only exercises the add path. That's precisely why the RV bug above is green in CI. countingProfileClient.GetContainerProfile also ignores its name argument, so no reconciler test can currently distinguish base from authored CP. Related: countingMetrics.legacyLoad is now unused and will likely trip unused.

The * classification fix looks right

Checked 903443d against storage: analyze_opens.go:97 uses the same Wildcard || Dynamic test, so this aligns node-agent with the producer, and compareSegmentsIndex only treats a whole segment * as a wildcard — so a path with an embedded literal * still matches literally and no allow-list is broadened. It applies to Execs and Endpoints too, not just Opens; harmless for Endpoints (both Values and Patterns go through CompareDynamic), but the new test only covers Opens — an Execs case would pin it.

The Test_33 R0002 fix (8c82261) is a good catch: a test that had never run in CI and was silently passing every "expect alert" case.

Nits with no anchorable hunk

  • reconciler.go:224 still says "UserAPRV + UserNNRV all match"; rebuildEntryFromSources' doc comment (:436-437) still describes the "label-referenced user overlay AP+NN" ladder pass that no longer exists.
  • component_test.go:2343 still refers to UserAPRef. Test_28's const overlayName = "curl-28-overlay" is now dead, and the comment above it still describes the AP+NN dual lookup — same in nginx-user-defined-deployment.yaml:17-18.
  • tests/resources/network-wildcards/README.md was deleted, but the NN fixtures it documented are still consumed by pkg/rulemanager/cel/libraries/networkneighborhood/fixtures_test.go:227 — those are now undocumented.
  • No metric or log when an authored CP is adopted; the legacy path emitted overlay metrics, so the new path is a blind spot.

Verified

go build ./pkg/objectcache/..., go test ./pkg/objectcache/... ./tests/resources/, and go vet -tags component ./tests/... all pass on the branch. The two behavioural claims above (frozen entry, forced-complete state) were each confirmed with a purpose-written test run against this branch, then discarded.

Comment thread pkg/objectcache/containerprofilecache/containerprofilecache.go
Comment thread pkg/objectcache/containerprofilecache/containerprofilecache.go
// LEGACY mixed in
// Need SOMETHING to cache. If we have nothing, stay pending and retry.
if cp == nil && userManagedAP == nil && userManagedNN == nil && userAP == nil && userNN == nil {
if cp == nil && userDefinedCP == nil && userManagedAP == nil && userManagedNN == nil {

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.

This is where a legacy user goes silently unprotected after upgrade: label set → learning suppressed → CP fetch 404s → no ug- profiles → we return false, the container stays pending forever with no profile, and the only trace is a Debug line.

Please emit a Warning plus a metric when the label is set but nothing resolved, so an upgrade that strands a legacy user is visible rather than silent.

(The ug- path itself isn't affected by this PR — still fetched at :335-368, still merged at :452-457. What's removed is the label-referenced AP/NN pair.)

Comment on lines 380 to 381
return false
}

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.

This gate (from :375) runs against the learned CP and returns before the authored CP is fetched at :391. So if the label is added to a workload that already has a CP mid-learning, the authored profile is never adopted until that CP reaches a terminal status — even though it's meant to replace it outright. refreshOneEntry:326 has the same ordering.

Simplest fix: fetch the authored CP first and skip this gate entirely when one is present — the learned CP is discarded at :419-421 anyway.

Comment on lines 451 to 452
// LEGACY
userManagedApplied := userManagedAP != nil || userManagedNN != nil

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.

Since cp may now be the authored profile (:419-421), this pass lets a legacy ug-<workload> AP/NN widen a profile the comment at :415-418 calls authoritative. reconciler.go:487-497 does the same.

The comments assert both things — :415-418 says the ug- pass "may still union on top", while :489-493 calls the authored CP "authoritative and complete by definition". Worth picking one deliberately, given ug- is part of the same legacy surface being decommissioned. My read: an authored profile shouldn't be silently widened by a legacy object the author may not know exists.

Minor, same hunk: // LEGACY here replaced the comment explaining the ug- naming convention and the Test_12/13 contract. Same at :332 and :409 (that one has trailing whitespace).

// userCP, when non-nil, is returned by GetContainerProfile for a name
// matching userCP.Name (the migrated user-defined ContainerProfile). Other
// names fall through to cp. Lets tests exercise the new-way overlay path.
userCP *v1beta1.ContainerProfile

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.

Inserting userCP (plus its comment) into this aligned field block breaks gofmt — the surrounding ap/nn/cpErr/apErr/nnErr lines need re-padding, and cp needs un-padding.

gofmt -l ./pkg/objectcache/containerprofilecache/ flags this file on the branch but not on main, so it's newly introduced here. gofmt -w on the file fixes it.

Two more in containerprofilecache.go, also new on this branch: a double blank line at :330-331, and trailing whitespace on // LEGACY mixed in at :409. (The struct-alignment warning gofmt reports at :111-113 in that file is pre-existing on main — not yours.)

No CI job runs gofmt, so none of this fails the build; it'll just show up in the next person's editor.

entlein and others added 4 commits July 28, 2026 17:05
…storage etc

Signed-off-by: entlein <einentlein@gmail.com>
…icontainer labels, tests not reviewed yet

Signed-off-by: entlein <einentlein@gmail.com>
Signed-off-by: entlein <einentlein@gmail.com>
Co-authored-by: Matthias Bertschy <matthias.bertschy@gmail.com>
Signed-off-by: Duck <70207455+entlein@users.noreply.github.com>
@entlein

entlein commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Component-test validation (live k3s)

Ran the migration CT matrix on a live k3s cluster using the component-test chart pinned to freshly built node-agent + storage images, with CI-faithful config (short learning intervals, R0040 defined + enabled, R0002 self-enabled at runtime, alerts collected via Alertmanager).

go test -tags=component -run 'Test_27|Test_28|Test_32|Test_33|Test_34|Test_35'all pass, 41/41 subtests, 0 failures (~34 min):

Test Result
Test_27_ApplicationProfileOpens PASS — 7/7 (incl. recorded_profile_absolute_paths)
Test_28_UserDefinedNetworkNeighborhood PASS — 6/6 (incl. all three R0011 raw-IP egress cases)
Test_32_UnexpectedProcessArguments PASS — 10/10
Test_33_AnalyzeOpensWildcardAnchoring PASS — 11/11
Test_34_NetworkNeighborsCIDRCollapse PASS
Test_35_MultiContainerPerContainerBinding PASS

Review feedback addressed:

  • Per-container bindingTest_35 pins it live: a two-container pod with inverse per-container ContainerProfiles resolved as <label>-<containerName>. R0001 fires for the binary forbidden by each container's own profile and stays silent for the one it allows, so there is no cross-container inheritance. A bare-<label> fallback keeps single-container pods working.
  • entry.RV freeze — the reconciler now tracks the learned CP's ResourceVersion (empty for a user-defined-only container). A refresh-path unit test pins the contract that an edit to the authored CP is picked up, which was previously frozen by comparing the authored RV against a 404 on the learned slug.
  • Ordering — the authored CP is fetched before the terminal-status gate on both the add and refresh paths, so adopting an authored profile no longer waits on a mid-learning consolidated CP.
  • Authored-CP validation — a label-referenced CP carrying lifecycle annotations (i.e. a learned profile) is ignored rather than force-completed.
  • Silent-upgrade visibility — a Warning plus a metric are emitted when the label is set but resolves to nothing.
  • Legacy user-managed merge removed — it had no consumers; the user-defined authoritative ContainerProfile is the sole user-profile mechanism.

Entlein and others added 4 commits July 29, 2026 20:48
…ace)

The rulemanager CEL libraries already read ContainerProfile data but still
carried AP/NN naming. Rename applicationprofile -> containerprofile and
networkneighborhood -> containerprofilenetwork (dirs via git mv, packages,
types, constructors, LibraryName), and unify the CEL function namespace:
ap.* and nn.* -> cp.* (cp.was_executed, cp.is_domain_in_egress, ...). Update
cel.go registration, the bundled default-rules.yaml, and the network-wildcards
test rules to the cp.* namespace so rules compile against the renamed engine.
Rule content mirrors the rulelibrary migrate/sbob rename.

Signed-off-by: entlein <einentlein@gmail.com>
The CP-form fixtures lived in a parallel network-wildcards-cp/ dir alongside the
now-dead network-wildcards/. 'cp' is an implementation detail, not part of what
the fixtures are. Remove the dead dir, move the CP fixtures to network-wildcards/
(update the nnlint/fixtures test references), and drop the explanatory README.

Signed-off-by: entlein <einentlein@gmail.com>
Signed-off-by: entlein <einentlein@gmail.com>
@entlein

entlein commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

rc5k validation — component tests, benchmark, chart

Component tests — fork overlay harness, node-agent + storage both built from migrate/sbob at sbob-rc5k. Run: https://github.com/k8sstormcenter/node-agent/actions/runs/30482336177
25 pass / 3 fail. The three failures are the known CI-infra flakes (Test_09 false-positive, Test_20 / Test_21 partial-then-learn timing) — reproduced on prior clean runs, not code. All migration legs green: Test_15, Test_27, Test_28, Test_32, Test_33, Test_34, Test_35. Test_08 now runs as Test_08_ContainerProfilePatching (renamed from ApplicationProfilePatching; flat-spec patch contract + read-back assertions) and passes.

Benchmark — run https://github.com/k8sstormcenter/node-agent/actions/runs/30485745861, quality gate passed. Swaps only nodeAgent.image (before = upstream v0.3.158, after = sbob-rc5k); everything else chart-default:

node-agent before after Δ
memory 330.0 MiB 260.9 MiB −20.9%
CPU 0.214 cores 0.214 cores flat

21 steady-state samples each. Storage runs chart-default upstream in both arms, so it is not the variable under test.

Chartkubescape-operator 1.40.3-sbob-rc5k released (node-agent + storage images on ghcr).

@matthyx

matthyx commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

I like your chicken

@matthyx matthyx moved this to WIP in KS PRs tracking Jul 30, 2026
@entlein

entlein commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

rc5l released — GeneratedNetworkPolicy fix + backend/synchronizer migration

rc5l supersedes rc5k (rc5k's GeneratedNetworkPolicy was broken — see the correction above and storage d185908d). kubescape-operator 1.40.3-sbob-rc5l published (node-agent + storage ghcr images).

Component tests (run https://github.com/k8sstormcenter/node-agent/actions/runs/30518037316): 25 pass / 3 fail — the 3 are the known infra flakes (Test_09 false-positive, Test_20/Test_21 partial-then-learn timing); all migration legs green; Test_08_ContainerProfilePatching passes. No regression from the singular-kind fix.

GeneratedNetworkPolicy fix — confirmed live on rc5l: a two-container Deployment yields exactly one workload-level deployment-nginx policy whose egress unions both containers (10.0.0.1/32:80 + 10.0.0.2/32:443). rc5k returned NotFound for the same case (queried the plural containerprofiles kind; storage keys CPs under the singular containerprofile).

backend + synchronizer migrated to ContainerProfile as well (AP/NN removed, storage pinned v0.0.290): backend's gRPC proto regenerated CP-only; synchronizer's filter + integration tests moved to ContainerProfile.

Benchmark unchanged from rc5k (node-agent binary identical in rc5l): node-agent memory −20.9% vs upstream v0.3.158, CPU flat.

@entlein

entlein commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Test-coverage gaps to close — ContainerProfile migration (node-agent)

The cache half is well-covered: pkg/objectcache/containerprofilecache (83.6%) has real contract tests for the authored-CP-vs-learned gate, the learned-RV freeze on the direct-patch path, per-container binding + bare fallback, and the projection/trie/wildcard merge. No action there.

The risk is the rule-engine halfpkg/rulemanager is 0–5.5% and validated only by cluster-only component tests, so a migration wiring bug (stale AP/NN read, wrong projection handoff, wrong profile-complete gate) has no fast unit tripwire.

HIGH

  • rulemanager/cel/libraries/containerprofile/http.go0%, no test file. Six wired cp.* HTTP evaluators (wasEndpointAccessed, …WithMethod/WithMethods/WithPrefix/WithSuffix, wasHostAccessed). Table test per fn (match / no-match / method-mismatch / prefix+suffix boundary / empty-endpoints). Cluster-free — highest value, lowest effort.
  • rulemanager/rule_manager.go: HasFinalApplicationProfile (the renamed AP→CP profile-complete enforce-vs-learn gate), recompileProjectionSpec (user-managed + user-defined overlay onto the base CP + the SetProjectionSpec handoff), EvaluatePolicyRulesForEvent0%, component-only. Fake-CP-cache unit tests: Completed+Full → enforce, non-terminal → skip; spec change re-projects.
  • rulemanager/profilehelper (GetProjectedContainerProfile/GetPodSpec/GetContainerName) — 0%; rulemanager/containercallbacks.go (ContainerCallback/monitorContainer/waitForSharedContainerData) — 0%. The accessor + per-container cache wiring every CP-aware rule uses.
  • cel/libraries/containerprofile/exec.go isExecInPodSpec12%; the podspec-exec allow-path (image entrypoint/command exempt; feeds wasExecuted/wasExecutedWithArgs) is untested — only the error-return branch is hit.

MED

  • cel/libraries/containerprofilenetwork/network.go (isDomainInEgress/Ingress, wasAddressInEgress/Ingress) — 66.7% happy-only; add no-match / wildcard-domain / empty-CP cases.
  • rulemanager/ruleadapters/creator.go setProfileMetadata0% (stamps the CP name/status/completion — renamed fields — onto rule failures).
  • containerprofilecache/reconciler.go rebuildEntryFromSources partial/failure branches; cel/…/ap.go Declarations 44.9% (≈half the cp.* overloads never built — a registration regression wouldn't be caught).

LOW

  • containerprofilecache.go synthetic-CP block (~:449-467) appears unreachable in tryPopulateEntry (both-nil returns earlier; authored path sets cp first) — verify reachability, then test or remove.

Resolve the Test_35 collision: upstream added Test_35_ExecTTYFieldTest at the
same location as our Test_35_MultiContainerPerContainerBinding. Keep both;
renumber ours to Test_36_MultiContainerPerContainerBinding.
@entlein

entlein commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Test_20 / Test_21 — why they time out, and how to rewrite them

These two are not the SQLite write-contention flake (that's Test_09, which shows ~100+ database is locked). Both instead hang to the 20-minute global test panic inside a WaitForContainerProfileCompletion* call, with no assertion — the failure output tells you nothing.

Test_20_AlertOnPartialThenLearnProcessTest — panics in WaitForContainerProfileCompletionWithBlacklist. The storage log shows the same profile replicaset-nginx-deployment-<hash>-nginx-… reported is blacklisted; retry in: 10s on every poll until the 20m timeout. After the mid-test deployment restart, the test blacklists the old profile name and waits for a new completed one — but no fresh, non-blacklisted completed profile ever appears within budget, so it spins forever. (0 database is locked in this run — a test-design race, not storage contention.)

Test_21_AlertOnPartialThenLearnNetworkTest — panics in the first WaitForContainerProfileCompletion. The workload is multiple-containers-deployment, and its profile replicaset-multiple-containers-…-nginx-… never reaches completed, so it hangs before it even gets to the alert phase. (Only 3 database is locked here — mild, but it compounds the multi-container completion timing.)

The shared problem: both hinge on a natural learn → restart → re-learn cycle reaching completed within an implicit budget, and when it doesn't they don't fail — they hang to the 20m panic with zero diagnostics. Test_20's "blacklist the old profile, wait for a new one" step is additionally racy against the per-container / replicaset-hash ContainerProfile naming the migration introduced.

How to rewrite:

  1. Fail fast with diagnostics, never a bare 20m panic. Give each WaitForContainerProfileCompletion* an explicit bounded deadline that, on timeout, dumps the current profile name + status (and the storage logs) and fails with a clear message — so a stuck learn surfaces "profile X stuck in status Y" seconds after the budget, not 20 minutes later with nothing.
  2. Make "partial" and "re-learned" deterministic instead of raced. Rather than restarting the daemonset/deployment to catch a partial profile, drive the states explicitly with an authored / user-defined ContainerProfile (the Test_28 pattern): apply a partial profile → assert the alert fires → update the authored profile to include the action → assert no alert. This removes the learning-timing dependency and the blacklist/rename race entirely.
  3. Poll the real condition; drop the fixed time.Sleeps. Replace Sleep(15s) + a single GetAlerts with require.Eventually on "the expected alert appeared" / "the alert stopped appearing", so the test adapts to variable learning latency instead of assuming a fixed window.
  4. Split the two assertions. "Alerts on a partial profile" and "stops alerting once the action is learned" as separate focused tests (or independently-budgeted sub-phases), so a failure names which behavior broke instead of hanging in phase 2.
  5. Test_21 specifically: use a single-container workload for the partial-then-learn behavior (the multi-container aspect is orthogonal and only adds completion-timing fragility), or explicitly wait for the full set of per-container profiles and blacklist all of them.

Net: the Test_20/21 timeouts are a test-robustness problem (no fail-fast, raced state transitions, fixed sleeps) — distinct from the storage single-writer contention that Test_09 exposes. Rewriting to deterministic authored-profile states + fail-fast waits fixes both and makes them robust to the contention as a side effect.

@entlein

entlein commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

rc5o — component tests + benchmark

Rebuilt both node-agent + storage from migrate/sbob HEAD and re-ran the full overlay CT, now exercising the deterministic Test_20/21 rewrite, the Test_35Test_36 rename, and the new upstream Test_35_ExecTTYFieldTest.

Component testshttps://github.com/k8sstormcenter/node-agent/actions/runs/3055046785626 pass / 2 fail.

  • Rewritten Test_20_AlertOnPartialThenLearnProcessTest + Test_21_AlertOnPartialThenLearnNetworkTest: pass (previously 20-minute timeouts — they now enforce an authored partial→full ContainerProfile instead of racing natural learning).
  • Test_36_MultiContainerPerContainerBinding (renamed) + Test_35_ExecTTYFieldTest (new upstream): pass. Every migration leg green (15/27/28/32/33/34/36).
  • The 2 failures (Test_09, Test_16) are the same storage single-writer SQLite contention (database is locked drops a profile-completion write under load) — not a migration regression, and different tests catch it on different runs. Test_16 is additionally fragile: it has no explicit profile-completion wait (fixed sleeps only). Exact RCA in progress.

Benchmarkhttps://github.com/k8sstormcenter/node-agent/actions/runs/30553652590 — quality gate passed. Swaps only nodeAgent.image (before = upstream v0.3.158, after = sbob-rc5o); everything else chart-default:

node-agent before after Δ
memory 312.4 MiB 257.9 MiB −17.4%
CPU 0.197 cores 0.193 cores ~flat

21 steady-state samples each. Storage runs chart-default upstream in both arms, so it is not the variable under test.

@entlein

entlein commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

rc5p — CT after the Test_16 completion-gate fix

https://github.com/k8sstormcenter/node-agent/actions/runs/3056028463627 pass / 1 fail.

Test_16_ApNotStuckOnRestart now passes in CI: it no longer sleeps a fixed 175s and gambles that the profile is enforcing — it gates the ls violation on the merged ContainerProfile actually reaching completed after the restart (bounded, fail-fast). Together with the deterministic Test_20/21 rewrites, that removes the three fixed-sleep/blacklist races. Test_36 (renamed) + Test_35_ExecTTYFieldTest (new upstream) green; every migration leg green.

The single remaining failure is Test_09_FalsePositiveTest — the genuine storage single-writer SQLite contention (database is locked under full-CT concurrency drops a profile-completion write; this run again shows the lock events). Unlike Test_16/20/21 it doesn't gate on a controllable test condition — the honest fix is the storage write-serialization change (deferred), not a test tweak.

@entlein

entlein commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Correction to the Test_09 characterization above: this rc5p run had 0 literal database is locked, but 41 related storage-write failures — Create - save object failed, context deadline exceeded on lock/pool acquisition, and [SHOULD NOT HAPPEN] failed to update managedFields. Same root (the storage single-writer contention: writes and lock/pool acquisition back up under full-CT load), different symptom string.

Effect: the checkoutservice ContainerProfile stayed not in status 'completed', so the legitimate workload wasn't enforced-against and produced 16 false-positive alerts (component_test.go:555, the test expects 0). So Test_09 remains the storage-contention flag — the contention just surfaced as lock-acquisition timeouts + dropped saves this run rather than the raw database is locked message. The structural fix is the deferred storage write-serialization change; Test_09, unlike Test_16/20/21, doesn't gate on a controllable test condition.

Entlein added 3 commits July 30, 2026 21:18
Add cluster-free unit coverage for the migration's rule-side surface:
- cp.* HTTP evaluators (http.go, was 0%/no file): endpoint/host match, method
  and prefix/suffix variants, empty-CP, nil-cache error.
- isExecInPodSpec + the wasExecuted/wasExecutedWithArgs podspec-exempt fall-through.
- containerprofilenetwork matchers: no-match / wildcard-domain / empty-neighborhood.
- Declarations smoke+drift guard: compile every cp.* overload, assert the decl set.
- profilehelper GetProjectedContainerProfile/GetPodSpec/GetContainerName.
- HasFinalApplicationProfile enforce-vs-learn gate (Completed+Full -> enforce).

containerprofile 37->69%, containerprofilenetwork 75->80%, profilehelper 95%,
HasFinalApplicationProfile 100%. Test-only; no production changes.

Signed-off-by: entlein <einentlein@gmail.com>
… racy learning)

Test_20/21 hung to the 20-minute global panic in WaitForContainerProfileCompletion*:
Test_20's blacklist wait can never succeed (the old completed profile stays
matched and blacklisted after the deployment restart), and Test_21's
multi-container profile never reached 'completed'. Both raced the natural
learn -> daemonset-restart -> deployment-restart -> re-learn cycle.

Rewrite to enforce an AUTHORED user-defined ContainerProfile, updated in place:
an action not in the profile alerts; the same action, once added, does not.
Determinism comes from a positive reload gate -- the update that adds the
subject also removes a canary, so the canary starts alerting the moment
node-agent reloads the revision (alert-appears signal), confirming the reload
before the negative assertion. Single-container workloads; bounded pollers that
dump ContainerProfile status on timeout (never a 20m panic); Eventually-style
polling on the real alert condition instead of fixed sleeps.

Also fixes a latent bug: the old 'no new alert' check filtered on a
process_name label that alerts never carry, so it always matched zero and
passed regardless; the rewrite keys on comm (process) and rule_id (network).

Verified: both pass twice on a live rig (rc5l), 0 'database is locked'. This now
tests profile ENFORCEMENT (authored partial->full), not natural learning.

Signed-off-by: entlein <einentlein@gmail.com>
… sleep

Test_16 slept a hard-coded 175s after the restart-inducing 'service nginx stop'
before exec'ing the ls violation (the WaitForContainerProfileCompletion guard was
commented out), then asserted the 'Unexpected process launched' alert. On a quiet
system that had ~110s of slack; under full-CT concurrency the storage single-writer
SQLite contention (database is locked) defers the merged profile's completion-status
write past the fixed window, so ls ran against a non-enforcing profile and no alert
fired -> the assertion failed (observed as Test_16 red in a full CT with 12 lock events).

Replace the fixed sleep with a bounded poll (5m, fail-fast, dumps ContainerProfiles
on timeout) for the MERGED ContainerProfile reaching 'completed' after the restart --
keyed on the merged profile only (name has no -<32 hex> suffix), not
WaitForContainerProfileCompletion which requires ALL matching profiles completed and
would hang on the transient per-instance profiles the restart spawns. Then exec ls
and poll (Eventually) for the alert. Validated 3/3 on a live rig; runtime ~241s -> ~90s
since it no longer over-sleeps.

Signed-off-by: entlein <einentlein@gmail.com>
@entlein

entlein commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

@CodeRabbit your turn, please review together with kubescape/storage#351
and take into account https://github.com/k8sstormcenter/node-agent/actions/runs/30560284636
and the helm chart PR 888
and the backend PR kubescape/backend#55
and the synchronizer PR kubescape/synchronizer#168

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== node-agent PR `#864` ==='
gh pr view 864 --repo kubescape/node-agent --json url,title,state,body,commits,files,comments \
  --jq '{url,title,state,body,files:[.files[]|{path,additions,deletions}],comments:[.comments[]|{author:.author.login,body,url}]}'

for pr in \
  'kubescape/storage 351' \
  'kubescape/backend 55' \
  'kubescape/synchronizer 168'
do
  set -- $pr
  repo="$1"
  number="$2"
  echo "=== ${repo} PR #${number} ==="
  gh pr view "$number" --repo "$repo" --json url,title,state,body,baseRefName,headRefName,commits,files \
    --jq '{url,title,state,baseRefName,headRefName,body,commits:[.commits[]|{oid,messageHeadline}],files:[.files[]|{path,additions,deletions}]}'
done

echo '=== cited CI run ==='
gh run view 30534641643 --repo k8sstormcenter/node-agent --json url,status,conclusion,event,headSha,displayTitle,jobs \
  --jq '{url,status,conclusion,event,headSha,displayTitle,jobs:[.jobs[]|{name,status,conclusion}]}'

echo '=== chart PR links mentioned on node-agent PR `#864` ==='
gh pr view 864 --repo kubescape/node-agent --json body,comments \
  --jq -r '[.body, (.comments[]?.body // "")] | join("\n")' \
  | grep -Eio 'https://github\.com/[^[:space:])>]+/(helm-charts|kubescape-operator|[^/]+)/pull/[0-9]+' \
  | sort -u || true

Length of output: 61652


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== locate chart/operator migration PR candidates ==='
for repo in kubescape/helm-charts kubescape/kubescape-operator kubescape/operator; do
  echo "--- $repo ---"
  gh pr list --repo "$repo" --state all --limit 100 \
    --json number,url,title,state,headRefName,updatedAt \
    --jq '.[] | select((.headRefName // "") | test("migrate|sbob"; "i")) | {number,url,title,state,headRefName,updatedAt}' \
    || true
done

echo '=== node-agent module dependencies and component-workflow diff ==='
rg -n -C2 'storage|backend|synchronizer|softwarecomposition' go.mod go.sum || true
git diff --unified=80 'origin/main...HEAD' -- .github/workflows/component-tests.yaml

echo '=== cache and reconciler symbol map ==='
ast-grep outline pkg/objectcache/containerprofilecache/containerprofilecache.go \
  --items all --type function,method,struct --match 'tryPopulateEntry|buildEntry|CachedContainerProfile' --view expanded
ast-grep outline pkg/objectcache/containerprofilecache/reconciler.go \
  --items all --type function,method --match 'refreshOneEntry|rebuildEntryFromSources' --view expanded

echo '=== binding, validation, RV, and terminal-state code ==='
rg -n -C6 \
  'UserDefinedProfileMetadataKey|UserCPRef|UserCPRV|isUserDefined|managed-by|ResourceVersion|Completed|Full|containerName' \
  pkg/objectcache/containerprofilecache/containerprofilecache.go \
  pkg/objectcache/containerprofilecache/reconciler.go

echo '=== deterministic Test_20/Test_21 and completion-wait implementation ==='
rg -n -C5 \
  'Test_20_AlertOnPartialThenLearnProcessTest|Test_21_AlertOnPartialThenLearnNetworkTest|WaitForContainerProfileCompletion|Eventually|Deadline|Timeout' \
  tests/component_test.go tests || true

echo '=== API-removal residue in production Go code (excluding tests/mocks) ==='
rg -n --glob '*.go' --glob '!**/*_test.go' --glob '!mocks/**' \
  '\b(ApplicationProfile|NetworkNeighborhood|GetApplicationProfile|GetNetworkNeighborhood|ListApplicationProfiles|ListNetworkNeighborhoods)\b' \
  pkg cmd || true

Length of output: 149501


@entlein I reviewed this as the ContainerProfile-only change stack: node-agent #864, storage #351, backend #55, synchronizer #168, and Helm charts #888. The API-removal direction and node-agent’s storage v0.0.290 pin are aligned; the current source also contains the deterministic authored-profile rewrites for Test_20/21 and the bounded completion gate for Test_16.

I found one reliability gap in the per-container binding retry path:

  • tryPopulateEntry first fetches <label>-<containerName>, correctly falling back to bare <label> only on NotFound. However, if that first per-container fetch fails transiently, it records bare <label> as UserCPRef (Lines 330–377 and Line 503). refreshOneEntry then retries only UserCPRef.Name (Line 337), so it never retries the intended per-container name. A transient storage error can therefore permanently prevent loading an otherwise valid <label>-<containerName> profile—especially relevant given the documented storage contention. Preserve the resolved per-container candidate as the retry target on transient failures; reserve bare-name selection for a confirmed NotFound fallback.

The historical workflow #30534641643 is not fully green (Test_09/20/21 failed), but its migration-focused legs passed. The subsequent source changes address the Test_20/21 timing failures; Test_09 remains a storage write-contention concern outside this node-agent change.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@entlein
entlein marked this pull request as ready for review July 30, 2026 19:21

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/objectcache/containerprofilecache/reconciler.go (1)

334-350: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

A deleted authored ContainerProfile is never dropped from the cache.

Line 340 treats every userCPErr as transient when e.UserCPRV != "", including a genuine NotFound. If the user deletes the authored CP, refresh returns early on every tick and the entry keeps enforcing the deleted profile (forced Completed/Full) for the container's lifetime. The add path already distinguishes the two cases via apierrors.IsNotFound; mirror that here so a confirmed deletion falls through to the rebuild.

🐛 Proposed fix
-		if userCPErr != nil && e.UserCPRV != "" {
+		if userCPErr != nil && !apierrors.IsNotFound(userCPErr) && e.UserCPRV != "" {
 			logger.L().Debug("refreshOneEntry: user-defined CP fetch failed; keeping cached entry",
 				helpers.String("containerID", id),
 				helpers.String("name", e.UserCPRef.Name),
 				helpers.Error(userCPErr))
 			return
 		}
🤖 Prompt for 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.

In `@pkg/objectcache/containerprofilecache/reconciler.go` around lines 334 - 350,
Update refreshOneEntry’s user-defined ContainerProfile error handling to
distinguish apierrors.IsNotFound(userCPErr) from transient fetch failures.
Preserve the cached-entry early return for non-NotFound errors when e.UserCPRV
is set, but let confirmed NotFound errors fall through with userDefinedCP
cleared so the entry is rebuilt without the deleted profile.
🧹 Nitpick comments (12)
pkg/objectcache/containerprofilecache/reconciler.go (1)

413-420: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate learned-CP check.

rebuildEntryFromSources is only reached from refreshOneEntry, which already nils a learned userDefinedCP at Lines 355-362. Keeping both is harmless but the invariant is easier to maintain in one place (or extracted into a small isAuthoredCP helper shared by both paths and tryPopulateEntry).

🤖 Prompt for 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.

In `@pkg/objectcache/containerprofilecache/reconciler.go` around lines 413 - 420,
Remove the duplicate learned-profile filtering block from
rebuildEntryFromSources, relying on refreshOneEntry’s existing handling before
it invokes the rebuild path. Keep the authored-versus-learned check centralized
there, or extract it into a shared isAuthoredCP helper only if needed by
tryPopulateEntry.
pkg/rulemanager/cel/libraries/containerprofile/cache_test.go (1)

16-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale "ApplicationProfile" naming in test function names.

These test functions now exercise containerProfileLibrary/ContainerProfile/cp.*, but names (TestApplicationProfileCaching, TestApplicationProfileCacheDifferentArguments, TestApplicationProfileCacheExpiration, TestApplicationProfileCachePerformance, TestApplicationProfileCacheClearCache) still reference "ApplicationProfile", unlike sibling files (e.g. exec_test.go, open_test.go) which were renamed consistently.

✏️ Suggested renames
-func TestApplicationProfileCaching(t *testing.T) {
+func TestContainerProfileCaching(t *testing.T) {
...
-func TestApplicationProfileCacheDifferentArguments(t *testing.T) {
+func TestContainerProfileCacheDifferentArguments(t *testing.T) {
...
-func TestApplicationProfileCacheExpiration(t *testing.T) {
+func TestContainerProfileCacheExpiration(t *testing.T) {
...
-func TestApplicationProfileCachePerformance(t *testing.T) {
+func TestContainerProfileCachePerformance(t *testing.T) {
...
-func TestApplicationProfileCacheClearCache(t *testing.T) {
+func TestContainerProfileCacheClearCache(t *testing.T) {

Also applies to: 156-156, 239-239, 315-315, 391-391

🤖 Prompt for 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.

In `@pkg/rulemanager/cel/libraries/containerprofile/cache_test.go` at line 16,
Rename the five test functions in cache_test.go from the
ApplicationProfile-prefixed names to ContainerProfile equivalents:
TestContainerProfileCaching, TestContainerProfileCacheDifferentArguments,
TestContainerProfileCacheExpiration, TestContainerProfileCachePerformance, and
TestContainerProfileCacheClearCache. Keep their test implementations unchanged.
pkg/rulemanager/cel/libraries/containerprofile/ap.go (1)

345-409: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the orphaned CostEstimator cases.

cp.was_internal_endpoint_accessed, cp.was_external_endpoint_accessed, cp.was_endpoint_accessed_with_direction, cp.was_endpoint_accessed_with_header, and cp.was_endpoint_accessed_with_header_value are only referenced in EstimateCallCost; they are not declared in the library, so CEL cannot compile calls to them. Drop these dead branches unless these functions are intended to be added to Declarations().

🤖 Prompt for 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.

In `@pkg/rulemanager/cel/libraries/containerprofile/ap.go` around lines 345 - 409,
Remove the five orphaned switch branches from
containerProfileCostEstimator.EstimateCallCost:
cp.was_internal_endpoint_accessed, cp.was_external_endpoint_accessed,
cp.was_endpoint_accessed_with_direction, cp.was_endpoint_accessed_with_header,
and cp.was_endpoint_accessed_with_header_value. Leave the declared function
cases and default nil behavior unchanged; do not add declarations.
tests/component_test.go (4)

935-941: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated completion wait (same as Test_01).

Lines 940-941 call WaitForContainerProfileCompletion(160) twice; with AP/NN unified the second call is a no-op. Drop one.

🤖 Prompt for 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.

In `@tests/component_test.go` around lines 935 - 941, Remove the duplicated second
call to wl.WaitForContainerProfileCompletion(160) in this test, leaving a single
completion wait after restarting the node-agent daemonset while preserving the
existing error assertion.

2506-2523: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Two small leftovers in the new authored-profile helper path.

  • Line 2535: overlayName is now dead — the profile name comes from resources/containerprofile-user-defined-network.yaml. Remove it.
  • applyUserDefinedContainerProfile returns the pre-Create local, so the returned object has no UID/ResourceVersion. Every caller discards it today; either return the object from Create or drop the return value.

Also applies to: 2529-2539

🤖 Prompt for 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.

In `@tests/component_test.go` around lines 2506 - 2523, The authored-profile
helper path still contains an unused overlayName variable, and
applyUserDefinedContainerProfile returns the pre-create object without
server-populated metadata. Remove overlayName, then either return the object
produced by ContainerProfiles(ns).Create or change the helper to have no return
value and update its callers accordingly.

29-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

metav1 and v1 are two aliases of the same package.

k8s.io/apimachinery/pkg/apis/meta/v1 is imported twice, and the file then mixes metav1.GetOptions{} and v1.GetOptions{} for identical calls (e.g. Lines 1051-1054). Dropping the v1 alias and normalising on metav1 removes the ambiguity.

🤖 Prompt for 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.

In `@tests/component_test.go` around lines 29 - 30, Remove the duplicate v1 alias
import from tests/component_test.go and normalize all references to the
Kubernetes meta/v1 package on the existing metav1 alias, including GetOptions
usages in the affected test code.

75-85: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant duplicated completion wait with stale AP/NN wording.

With AP and NN merged into one ContainerProfile, the second WaitForContainerProfileCompletion(80) is a no-op, and the messages still say "application profile" / "network neighborhood". Collapse to one wait and reword.

🧹 Proposed cleanup
-	err = wl.WaitForContainerProfileCompletion(80)
-	require.NoError(t, err, "Error waiting for application profile to be completed")
-	err = wl.WaitForContainerProfileCompletion(80)
-	require.NoError(t, err, "Error waiting for network neighborhood to be completed")
+	err = wl.WaitForContainerProfileCompletion(80)
+	require.NoError(t, err, "Error waiting for container profile to be completed")
🤖 Prompt for 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.

In `@tests/component_test.go` around lines 75 - 85, In the test flow around
WaitForContainerProfileCompletion, remove the duplicated second wait and retain
a single completion wait. Update its assertion message to reference the merged
ContainerProfile rather than application profile or network neighborhood
wording, leaving the subsequent sleep and profile retrieval unchanged.
tests/resources/network-wildcards/18-cluster-dns-via-mid-ellipsis.yaml (1)

31-31: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale "this NN" wording.

Reads "declare the equivalent via this NN" — the fixture is a ContainerProfile now.

🤖 Prompt for 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.

In `@tests/resources/network-wildcards/18-cluster-dns-via-mid-ellipsis.yaml` at
line 31, Update the comment near the ContainerProfile fixture to replace the
stale “this NN” wording with “this ContainerProfile,” preserving the comment’s
intended meaning.
tests/testutils/k8s.go (1)

354-377: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

WaitForContainerProfileCompletionWithBlacklist duplicates WaitForContainerProfile.

The only delta is the name check; the status loop, backoff config, and notify callback are copies. Consider a single private waitForContainerProfile(maxRetries, expectedStatus, blacklist) with the two exported wrappers delegating to it, so a future change to the gate (e.g. filtering transient per-instance profiles) applies to both.

🤖 Prompt for 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.

In `@tests/testutils/k8s.go` around lines 354 - 377, Refactor
WaitForContainerProfileCompletionWithBlacklist and the existing
WaitForContainerProfile to delegate to one private waitForContainerProfile
helper accepting maxRetries, expectedStatus, and blacklist. Move the shared
matching, status validation, retry/backoff configuration, and notification logic
into the helper; preserve each wrapper’s current expected status and blacklist
behavior.
tests/resources/network-wildcards/14-recursive-star-rejected.yaml (1)

17-17: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale NetworkNeighborhood reference in the assertion checklist.

The fixture is now a ContainerProfile; the step-3 assertion text should say so.

🤖 Prompt for 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.

In `@tests/resources/network-wildcards/14-recursive-star-rejected.yaml` at line
17, Update the step-3 assertion checklist comment to refer to a ContainerProfile
named nw-14-recursive-rejected instead of a NetworkNeighborhood, matching the
fixture’s current resource type.
tests/resources/mc35-multi-container-userdefined-deployment.yaml (2)

24-27: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Pin the Debian image by digest.

debian:12-slim is mutable, so registry changes can make this multi-container component test non-reproducible. Use the tested digest, consistent with the digest-pinned fixtures elsewhere in this cohort.

🤖 Prompt for 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.

In `@tests/resources/mc35-multi-container-userdefined-deployment.yaml` around
lines 24 - 27, Replace the mutable debian:12-slim image references in the
multi-container deployment fixture, including the main container and sidecar,
with the tested Debian 12 slim image digest used by the corresponding
digest-pinned fixtures.

22-28: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Run this test workload without root or privilege escalation.

Both containers default to root and omit allowPrivilegeEscalation: false; neither sleep nor the exec checks require these privileges. Add a non-root UID, allowPrivilegeEscalation: false, and the runtime-default seccomp profile.

🤖 Prompt for 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.

In `@tests/resources/mc35-multi-container-userdefined-deployment.yaml` around
lines 22 - 28, Update both containers in the deployment around the app and
sidecar definitions to run as a non-root UID, disable privilege escalation, and
use the runtime-default seccomp profile. Apply equivalent security context
settings to each container without changing their images, commands, or other
workload behavior.

Source: Linters/SAST tools

🤖 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/objectcache/containerprofilecache/containerprofilecache.go`:
- Around line 330-379: Update the transient-error default branch in
containerprofilecache.go’s resolver to set resolvedOverlayName to
perContainerName, ensuring only confirmed NotFound uses the bare label retry
target. In containerprofilecache_test.go lines 225-249, change the
transient-error expectation to override-nginx and add coverage for per-container
NotFound followed by a transient bare-profile failure, expecting the bare
fallback target.

In `@tests/component_test.go`:
- Around line 1140-1155: Update the phase-2 setup around countR0001("ls") to
prove the R0001 cooldown budget was not exhausted during phase 1 before
asserting no new alert. Add an assertion that the pre-phase-2 ls count remains
below the per-container/per-rule cap of 10, while preserving the existing
phase-2 profile enforcement checks.
- Around line 729-743: The test should stop immediately when either profile
operation fails instead of continuing with invalid data. In the test flow around
WaitForContainerProfileCompletion and GetContainerProfile, replace both t.Errorf
checks with require.NoError assertions so containerProfile is only dereferenced
after successful retrieval.

In `@tests/resources/network-wildcards/20-multi-container-mixed-wildcards.yaml`:
- Around line 18-58: Add the required per-container binding labels to the paired
workload fixture for the frontend and sidecar containers, mapping each container
to its corresponding authored profile using the established profile-label key
format. Do not rely on the `# container:` comments or shared `matchLabels` for
profile resolution, and preserve the existing profile definitions.

In `@tests/testutils/k8s.go`:
- Around line 286-348: Filter transient per-instance profiles from the helpers
in tests/testutils/k8s.go lines 286-348 by preferring merged profiles in
GetContainerProfile and GetContainerProfiles; when selecting among merged
candidates, add a deterministic secondary name tiebreaker after creation time.
In tests/testutils/k8s.go lines 379-397, update the status gate to skip
non-merged profiles so transient failed or ready profiles do not block
completion.
- Around line 571-589: Update getEgressDnsNames and getIngressDnsNames to add
each neighbor’s singular DNS value as well as all entries in DNSNames, ensuring
profiles containing only DNS are included in the returned sets and assertion
checks cannot pass vacuously.

---

Outside diff comments:
In `@pkg/objectcache/containerprofilecache/reconciler.go`:
- Around line 334-350: Update refreshOneEntry’s user-defined ContainerProfile
error handling to distinguish apierrors.IsNotFound(userCPErr) from transient
fetch failures. Preserve the cached-entry early return for non-NotFound errors
when e.UserCPRV is set, but let confirmed NotFound errors fall through with
userDefinedCP cleared so the entry is rebuilt without the deleted profile.

---

Nitpick comments:
In `@pkg/objectcache/containerprofilecache/reconciler.go`:
- Around line 413-420: Remove the duplicate learned-profile filtering block from
rebuildEntryFromSources, relying on refreshOneEntry’s existing handling before
it invokes the rebuild path. Keep the authored-versus-learned check centralized
there, or extract it into a shared isAuthoredCP helper only if needed by
tryPopulateEntry.

In `@pkg/rulemanager/cel/libraries/containerprofile/ap.go`:
- Around line 345-409: Remove the five orphaned switch branches from
containerProfileCostEstimator.EstimateCallCost:
cp.was_internal_endpoint_accessed, cp.was_external_endpoint_accessed,
cp.was_endpoint_accessed_with_direction, cp.was_endpoint_accessed_with_header,
and cp.was_endpoint_accessed_with_header_value. Leave the declared function
cases and default nil behavior unchanged; do not add declarations.

In `@pkg/rulemanager/cel/libraries/containerprofile/cache_test.go`:
- Line 16: Rename the five test functions in cache_test.go from the
ApplicationProfile-prefixed names to ContainerProfile equivalents:
TestContainerProfileCaching, TestContainerProfileCacheDifferentArguments,
TestContainerProfileCacheExpiration, TestContainerProfileCachePerformance, and
TestContainerProfileCacheClearCache. Keep their test implementations unchanged.

In `@tests/component_test.go`:
- Around line 935-941: Remove the duplicated second call to
wl.WaitForContainerProfileCompletion(160) in this test, leaving a single
completion wait after restarting the node-agent daemonset while preserving the
existing error assertion.
- Around line 2506-2523: The authored-profile helper path still contains an
unused overlayName variable, and applyUserDefinedContainerProfile returns the
pre-create object without server-populated metadata. Remove overlayName, then
either return the object produced by ContainerProfiles(ns).Create or change the
helper to have no return value and update its callers accordingly.
- Around line 29-30: Remove the duplicate v1 alias import from
tests/component_test.go and normalize all references to the Kubernetes meta/v1
package on the existing metav1 alias, including GetOptions usages in the
affected test code.
- Around line 75-85: In the test flow around WaitForContainerProfileCompletion,
remove the duplicated second wait and retain a single completion wait. Update
its assertion message to reference the merged ContainerProfile rather than
application profile or network neighborhood wording, leaving the subsequent
sleep and profile retrieval unchanged.

In `@tests/resources/mc35-multi-container-userdefined-deployment.yaml`:
- Around line 24-27: Replace the mutable debian:12-slim image references in the
multi-container deployment fixture, including the main container and sidecar,
with the tested Debian 12 slim image digest used by the corresponding
digest-pinned fixtures.
- Around line 22-28: Update both containers in the deployment around the app and
sidecar definitions to run as a non-root UID, disable privilege escalation, and
use the runtime-default seccomp profile. Apply equivalent security context
settings to each container without changing their images, commands, or other
workload behavior.

In `@tests/resources/network-wildcards/14-recursive-star-rejected.yaml`:
- Line 17: Update the step-3 assertion checklist comment to refer to a
ContainerProfile named nw-14-recursive-rejected instead of a
NetworkNeighborhood, matching the fixture’s current resource type.

In `@tests/resources/network-wildcards/18-cluster-dns-via-mid-ellipsis.yaml`:
- Line 31: Update the comment near the ContainerProfile fixture to replace the
stale “this NN” wording with “this ContainerProfile,” preserving the comment’s
intended meaning.

In `@tests/testutils/k8s.go`:
- Around line 354-377: Refactor WaitForContainerProfileCompletionWithBlacklist
and the existing WaitForContainerProfile to delegate to one private
waitForContainerProfile helper accepting maxRetries, expectedStatus, and
blacklist. Move the shared matching, status validation, retry/backoff
configuration, and notification logic into the helper; preserve each wrapper’s
current expected status and blacklist behavior.
🪄 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 Plus

Run ID: bd9d1656-54f3-4164-9943-798afe0e2815

📥 Commits

Reviewing files that changed from the base of the PR and between fd5ed04 and e21c2f3.

📒 Files selected for processing (100)
  • .github/workflows/component-tests.yaml
  • mocks/readfiles.go
  • mocks/readfiles_test.go
  • mocks/testdata/collection_applicationprofiles.json
  • mocks/testdata/collection_networkneighborhood.json
  • mocks/testdata/nginx_applicationprofiles.json
  • mocks/testdata/nginx_networkneighborhood.json
  • pkg/metricsmanager/metrics_manager_interface.go
  • pkg/metricsmanager/metrics_manager_mock.go
  • pkg/metricsmanager/metrics_manager_noop.go
  • pkg/metricsmanager/otel/otel_metrics_manager.go
  • pkg/metricsmanager/prometheus/prometheus.go
  • pkg/objectcache/containerprofilecache/containerprofilecache.go
  • pkg/objectcache/containerprofilecache/containerprofilecache_test.go
  • pkg/objectcache/containerprofilecache/integration_helpers_test.go
  • pkg/objectcache/containerprofilecache/metrics.go
  • pkg/objectcache/containerprofilecache/projection.go
  • pkg/objectcache/containerprofilecache/projection_apply.go
  • pkg/objectcache/containerprofilecache/projection_apply_test.go
  • pkg/objectcache/containerprofilecache/projection_golden_test.go
  • pkg/objectcache/containerprofilecache/projection_test.go
  • pkg/objectcache/containerprofilecache/reconciler.go
  • pkg/objectcache/containerprofilecache/reconciler_test.go
  • pkg/objectcache/containerprofilecache/testdata/golden/network_all.json
  • pkg/objectcache/containerprofilecache/testdata/golden/rich_filtered.json
  • pkg/objectcache/containerprofilecache/testdata/golden/rich_passthrough.json
  • pkg/objectcache/v1/mock.go
  • pkg/rulemanager/cel/cel.go
  • pkg/rulemanager/cel/libraries/containerprofile/ap.go
  • pkg/rulemanager/cel/libraries/containerprofile/ap_smoke_test.go
  • pkg/rulemanager/cel/libraries/containerprofile/cache_test.go
  • pkg/rulemanager/cel/libraries/containerprofile/capability.go
  • pkg/rulemanager/cel/libraries/containerprofile/capability_test.go
  • pkg/rulemanager/cel/libraries/containerprofile/exec.go
  • pkg/rulemanager/cel/libraries/containerprofile/exec_podspec_test.go
  • pkg/rulemanager/cel/libraries/containerprofile/exec_test.go
  • pkg/rulemanager/cel/libraries/containerprofile/http.go
  • pkg/rulemanager/cel/libraries/containerprofile/http_test.go
  • pkg/rulemanager/cel/libraries/containerprofile/integration_test.go
  • pkg/rulemanager/cel/libraries/containerprofile/open.go
  • pkg/rulemanager/cel/libraries/containerprofile/open_bench_test.go
  • pkg/rulemanager/cel/libraries/containerprofile/open_test.go
  • pkg/rulemanager/cel/libraries/containerprofile/prestop_cache.go
  • pkg/rulemanager/cel/libraries/containerprofile/prestop_cache_test.go
  • pkg/rulemanager/cel/libraries/containerprofile/syscall.go
  • pkg/rulemanager/cel/libraries/containerprofile/syscall_test.go
  • pkg/rulemanager/cel/libraries/containerprofilenetwork/cache_test.go
  • pkg/rulemanager/cel/libraries/containerprofilenetwork/fixtures_test.go
  • pkg/rulemanager/cel/libraries/containerprofilenetwork/integration_test.go
  • pkg/rulemanager/cel/libraries/containerprofilenetwork/network.go
  • pkg/rulemanager/cel/libraries/containerprofilenetwork/network_coverage_test.go
  • pkg/rulemanager/cel/libraries/containerprofilenetwork/network_test.go
  • pkg/rulemanager/cel/libraries/containerprofilenetwork/nn.go
  • pkg/rulemanager/cel/libraries/containerprofilenetwork/wildcard_test.go
  • pkg/rulemanager/profilehelper/profilehelper_test.go
  • pkg/rulemanager/rule_manager_hasfinal_test.go
  • pkg/seccompmanager/v1/seccomp_manager_test.go
  • pkg/storage/storage_interface.go
  • pkg/storage/storage_mock.go
  • pkg/storage/v1/applicationprofile.go
  • pkg/storage/v1/networkneighborhood.go
  • pkg/storage/v1/storage.go
  • pkg/watcher/dynamicwatcher/watch.go
  • pkg/watcher/dynamicwatcher/watch_test.go
  • tests/chart/templates/node-agent/default-rules.yaml
  • tests/component_bulking_test.go
  • tests/component_test.go
  • tests/resources/containerprofile-fusioncore-network.yaml
  • tests/resources/containerprofile-user-defined-network.yaml
  • tests/resources/curl-exec-arg-wildcards-deployment.yaml
  • tests/resources/mc35-cp-app.yaml
  • tests/resources/mc35-cp-sidecar.yaml
  • tests/resources/mc35-multi-container-userdefined-deployment.yaml
  • tests/resources/network-wildcards/00-fusioncore-homoglyph-attack.yaml
  • tests/resources/network-wildcards/01-literal-ipv4.yaml
  • tests/resources/network-wildcards/02-literal-ipv6.yaml
  • tests/resources/network-wildcards/03-cidr-ipv4.yaml
  • tests/resources/network-wildcards/04-cidr-ipv6.yaml
  • tests/resources/network-wildcards/05-any-ip-sentinel.yaml
  • tests/resources/network-wildcards/06-any-as-cidr.yaml
  • tests/resources/network-wildcards/07-mixed-ip-list.yaml
  • tests/resources/network-wildcards/08-deprecated-ipaddress.yaml
  • tests/resources/network-wildcards/09-dns-literal.yaml
  • tests/resources/network-wildcards/10-dns-leading-wildcard.yaml
  • tests/resources/network-wildcards/11-dns-mid-ellipsis.yaml
  • tests/resources/network-wildcards/12-dns-trailing-star.yaml
  • tests/resources/network-wildcards/13-dns-trailing-dot-normalisation.yaml
  • tests/resources/network-wildcards/14-recursive-star-rejected.yaml
  • tests/resources/network-wildcards/15-egress-and-ingress.yaml
  • tests/resources/network-wildcards/16-egress-none.yaml
  • tests/resources/network-wildcards/17-realistic-stripe-api.yaml
  • tests/resources/network-wildcards/18-cluster-dns-via-mid-ellipsis.yaml
  • tests/resources/network-wildcards/19-port-protocol-with-cidr.yaml
  • tests/resources/network-wildcards/20-multi-container-mixed-wildcards.yaml
  • tests/resources/nginx-user-defined-deployment.yaml
  • tests/resources/nnlint_test.go
  • tests/resources/partial-network-deployment.yaml
  • tests/resources/partial-process-deployment.yaml
  • tests/resources/r0002-files-access-enabled.yaml
  • tests/testutils/k8s.go
💤 Files with no reviewable changes (16)
  • mocks/testdata/collection_networkneighborhood.json
  • mocks/testdata/nginx_applicationprofiles.json
  • mocks/testdata/nginx_networkneighborhood.json
  • pkg/storage/storage_interface.go
  • pkg/storage/v1/applicationprofile.go
  • mocks/testdata/collection_applicationprofiles.json
  • pkg/storage/v1/networkneighborhood.go
  • pkg/objectcache/containerprofilecache/metrics.go
  • mocks/readfiles_test.go
  • pkg/objectcache/containerprofilecache/projection.go
  • pkg/storage/storage_mock.go
  • pkg/seccompmanager/v1/seccomp_manager_test.go
  • pkg/objectcache/containerprofilecache/projection_test.go
  • pkg/objectcache/containerprofilecache/integration_helpers_test.go
  • pkg/objectcache/containerprofilecache/projection_apply.go
  • pkg/watcher/dynamicwatcher/watch.go

Comment thread pkg/objectcache/containerprofilecache/containerprofilecache.go
Comment thread tests/component_test.go Outdated
Comment thread tests/component_test.go
Comment thread tests/testutils/k8s.go
Comment thread tests/testutils/k8s.go
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Referenced PR metadata and changed files ==='
for item in \
  'kubescape/node-agent 864' \
  'kubescape/storage 351' \
  'kubescape/backend 55' \
  'kubescape/synchronizer 168'
do
  set -- $item
  repo="$1"
  number="$2"
  echo "--- ${repo}#${number} ---"
  gh pr view "$number" --repo "$repo" \
    --json url,title,state,isDraft,baseRefName,headRefName,commits,files \
    --jq '{url,title,state,isDraft,baseRefName,headRefName,commits:[.commits[]|{oid,messageHeadline}],files:[.files[]|{path,additions,deletions}]}'
done

echo '=== Locate and inspect Helm chart PR `#888` ==='
for repo in kubescape/helm-charts kubescape/kubescape-operator; do
  echo "--- ${repo}`#888` ---"
  gh pr view 888 --repo "$repo" \
    --json url,title,state,isDraft,baseRefName,headRefName,commits,files \
    --jq '{url,title,state,isDraft,baseRefName,headRefName,commits:[.commits[]|{oid,messageHeadline}],files:[.files[]|{path,additions,deletions}]}' \
    || true
done

echo '=== Cited component-test workflow ==='
gh run view 30560284636 --repo k8sstormcenter/node-agent \
  --json url,status,conclusion,event,headSha,displayTitle,jobs \
  --jq '{url,status,conclusion,event,headSha,displayTitle,jobs:[.jobs[]|{name,status,conclusion,startedAt,completedAt}]}'

echo '=== Primary PR: cache/reconciler source map ==='
ast-grep outline pkg/objectcache/containerprofilecache/containerprofilecache.go \
  --items all --type struct,function,method \
  --match 'CachedContainerProfile|tryPopulateEntry|buildEntry' --view expanded
ast-grep outline pkg/objectcache/containerprofilecache/reconciler.go \
  --items all --type function,method \
  --match 'refreshOneEntry|rebuildEntryFromSources' --view expanded

echo '=== Primary PR: per-container candidate, fallback, retry, and RV handling ==='
rg -n -C8 \
  'UserDefinedProfileMetadataKey|UserCPRef|UserCPRV|GetContainerProfile|IsNotFound|NotFound|containerName|ResourceVersion' \
  pkg/objectcache/containerprofilecache/containerprofilecache.go \
  pkg/objectcache/containerprofilecache/reconciler.go

echo '=== Primary PR: migration pins and production legacy API residue ==='
rg -n -C3 'storage|backend|synchronizer|softwarecomposition' go.mod go.sum || true
rg -n --glob '*.go' --glob '!**/*_test.go' --glob '!mocks/**' \
  '\b(ApplicationProfile|NetworkNeighborhood|GetApplicationProfile|GetNetworkNeighborhood|ListApplicationProfiles|ListNetworkNeighborhoods)\b' \
  pkg cmd || true

Length of output: 88251


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Node-agent: exact per-container lookup implementation ==='
sed -n '320,425p' pkg/objectcache/containerprofilecache/containerprofilecache.go
sed -n '280,390p' pkg/objectcache/containerprofilecache/reconciler.go

echo '=== Node-agent: focused tests for per-container fallback and transient retries ==='
rg -n -C6 \
  'per-container|PerContainer|overlayOnly|Transient|UserCPRef|UserDefinedCP|resolvedOverlayName' \
  pkg/objectcache/containerprofilecache/*_test.go

echo '=== Node-agent: component chart and workflow image/version wiring ==='
sed -n '1,180p' .github/workflows/component-tests.yaml
sed -n '1,240p' tests/chart/values.yaml 2>/dev/null || true
rg -n -C4 'storage|node-agent|synchronizer|image:|tag:' tests/chart .github/workflows/component-tests.yaml

echo '=== Storage `#351`: public API / ContainerProfile registration and completed-update guard ==='
gh pr diff 351 --repo kubescape/storage -- \
  pkg/apis/softwarecomposition/register.go \
  pkg/apis/softwarecomposition/v1beta1/generated.proto \
  pkg/registry/file/containerprofile_processor.go \
  pkg/registry/file/containerprofile_storage.go \
  pkg/registry/file/containerprofile_completed_guard_realmech_test.go \
  | sed -n '1,1000p'

echo '=== Backend `#55`: generated storage RPC and client API removals ==='
gh pr diff 55 --repo kubescape/backend -- \
  pkg/client/v1/proto/storage_service.proto \
  pkg/client/v1/storageclient.go \
  pkg/client/v1/storageclient_coverage_test.go \
  | sed -n '1,1000p'

echo '=== Synchronizer `#168`: resource filter and configured watched resources ==='
gh pr diff 168 --repo kubescape/synchronizer -- \
  adapters/incluster/v1/client.go \
  configuration/client/config.json \
  config/config_test.go \
  adapters/incluster/v1/client_cp_test.go \
  | sed -n '1,1000p'

echo '=== Helm charts `#888`: RBAC, synchronizer configuration, and image tags ==='
gh pr diff 888 --repo kubescape/helm-charts -- \
  charts/kubescape-operator/Chart.yaml \
  charts/kubescape-operator/values.yaml \
  charts/kubescape-operator/templates/node-agent/clusterrole.yaml \
  charts/kubescape-operator/templates/synchronizer/clusterrole.yaml \
  charts/kubescape-operator/templates/synchronizer/configmap.yaml \
  | sed -n '1,1200p'

Length of output: 90016


🏁 Script executed:

#!/bin/bash
set -euo pipefail

fetch_selected_patches() {
  local repo="$1"
  local number="$2"
  shift 2
  local wanted
  wanted="$(printf '%s\n' "$@")"

  gh api --paginate "repos/${repo}/pulls/${number}/files?per_page=100" \
    | jq -r --argjson names "$(printf '%s\n' "$wanted" | jq -R . | jq -s .)" '
        .[] | select(.filename as $f | $names | index($f)) |
        "===== \(.filename) =====\n\(.patch // "[patch unavailable]")\n"
      '
}

echo '=== Storage `#351` selected API/storage patches ==='
fetch_selected_patches kubescape/storage 351 \
  pkg/apis/softwarecomposition/register.go \
  pkg/apis/softwarecomposition/v1beta1/generated.proto \
  pkg/registry/file/containerprofile_processor.go \
  pkg/registry/file/containerprofile_storage.go \
  pkg/registry/file/containerprofile_completed_guard_realmech_test.go

echo '=== Backend `#55` selected RPC/client patches ==='
fetch_selected_patches kubescape/backend 55 \
  pkg/client/v1/proto/storage_service.proto \
  pkg/client/v1/storageclient.go \
  pkg/client/v1/storageclient_coverage_test.go

echo '=== Synchronizer `#168` selected watch/filter patches ==='
fetch_selected_patches kubescape/synchronizer 168 \
  adapters/incluster/v1/client.go \
  configuration/client/config.json \
  config/config_test.go \
  adapters/incluster/v1/client_cp_test.go

echo '=== Helm charts `#888` selected deployment wiring patches ==='
fetch_selected_patches kubescape/helm-charts 888 \
  charts/kubescape-operator/Chart.yaml \
  charts/kubescape-operator/values.yaml \
  charts/kubescape-operator/templates/node-agent/clusterrole.yaml \
  charts/kubescape-operator/templates/synchronizer/clusterrole.yaml \
  charts/kubescape-operator/templates/synchronizer/configmap.yaml

Length of output: 89906


@entlein I reviewed node-agent #864 together with storage #351, Helm charts #888, backend #55, synchronizer #168, and the cited component-test run.

The ContainerProfile-only API and deployment wiring are aligned: storage removes the legacy types, backend reserves the retired protobuf fields, synchronizer now lists/watches containerprofiles, and the chart removes AP/NN configuration and permissions. The cited run is green for the migration legs—including Test_16, Test_20/21, Test_27/28/32/33/34, and Test_36—with Test_09 remaining the isolated storage-contention failure.

I found one blocking reliability issue in node-agent:

  • Per-container retry target is lost after a transient fetch error. In pkg/objectcache/containerprofilecache/containerprofilecache.go, Lines 330–343 initialize resolvedOverlayName to the bare label. If the initial GET of <label>-<containerName> fails transiently, the code intentionally retains that bare name; it is then stored as UserCPRef (Line 503). refreshOneEntry only re-fetches UserCPRef.Name, so it will retry <label> forever and never retry the intended per-container profile. This can leave a multi-container workload on its learned profile—or pending—despite its authored <label>-<containerName> profile becoming available after storage recovers.

    Preserve the per-container candidate as UserCPRef on transient errors. Select and persist the bare label only after the per-container GET returns a genuine NotFound and the bare-name fallback is attempted. Update TestOverlayPath_CPFetchTransientError_RecordsUserCPRef to expect override-nginx, and add a refresh test showing the profile is adopted once that per-container GET succeeds.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@entlein

entlein commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Verdict on the Component Tests:
09 needs a fix that is not in scope for this PR
11 and 16 have some timing/race condition issues https://github.com/k8sstormcenter/node-agent/actions/runs/30619604575/job/91122031601 and https://github.com/k8sstormcenter/node-agent/actions/runs/30621700865 that can be "resolved" by re-running the test. Maybe we can make them better, but its not in scope of this PR

Signed-off-by: entlein <einentlein@gmail.com>

@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/metricsmanager/metrics_manager_mock.go`:
- Around line 70-71: Remove the duplicate MetricsMock.ReportDedupEvent method
declaration, keeping one implementation with the existing signature so the
package compiles.
🪄 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 Plus

Run ID: c254be1d-4408-4eb4-84ec-b48483430322

📥 Commits

Reviewing files that changed from the base of the PR and between 5bc3259 and f91d4a5.

📒 Files selected for processing (5)
  • pkg/metricsmanager/metrics_manager_interface.go
  • pkg/metricsmanager/metrics_manager_mock.go
  • pkg/metricsmanager/metrics_manager_noop.go
  • pkg/metricsmanager/otel/otel_metrics_manager.go
  • pkg/metricsmanager/prometheus/prometheus.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • pkg/metricsmanager/metrics_manager_interface.go
  • pkg/metricsmanager/otel/otel_metrics_manager.go
  • pkg/metricsmanager/metrics_manager_noop.go
  • pkg/metricsmanager/prometheus/prometheus.go

Comment on lines 70 to 71
func (m *MetricsMock) ReportDedupEvent(eventType utils.EventType, duplicate bool) {}
func (m *MetricsMock) ReportDedupEvent(eventType utils.EventType, duplicate bool) {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Remove the duplicate ReportDedupEvent declaration.

MetricsMock.ReportDedupEvent is declared twice with the same signature. Go rejects duplicate method declarations, so the package cannot compile. Keep only one declaration.

Proposed fix
 func (m *MetricsMock) ReportDedupEvent(eventType utils.EventType, duplicate bool) {}
-func (m *MetricsMock) ReportDedupEvent(eventType utils.EventType, duplicate bool) {}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (m *MetricsMock) ReportDedupEvent(eventType utils.EventType, duplicate bool) {}
func (m *MetricsMock) ReportDedupEvent(eventType utils.EventType, duplicate bool) {}
func (m *MetricsMock) ReportDedupEvent(eventType utils.EventType, duplicate bool) {}
🧰 Tools
🪛 GitHub Actions: Performance Benchmark / 0_benchmark.txt

[error] 71-71: Go build failed: method MetricsMock.ReportDedupEvent is declared more than once; it was already declared at line 70.

🪛 GitHub Actions: Performance Benchmark / benchmark

[error] 70-71: Go build failed: method MetricsMock.ReportDedupEvent is declared twice; the declaration at line 71 duplicates line 70. Failed command: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o node-agent ./cmd/main.go

🪛 GitHub Actions: pull_request_created / 0_pr-created _ test _ Create cross-platform build.txt

[error] 70-71: go test failed: method MetricsMock.ReportDedupEvent is declared twice. Remove the duplicate declaration.

🪛 GitHub Actions: pull_request_created / pr-created _ test _ Create cross-platform build

[error] 71-71: go test failed: method MetricsMock.ReportDedupEvent is declared more than once; it was already declared at line 70.

🪛 GitHub Check: benchmark

[failure] 71-71:
method MetricsMock.ReportDedupEvent already declared at pkg/metricsmanager/metrics_manager_mock.go:70:23

🪛 GitHub Check: pr-created / test / Create cross-platform build

[failure] 71-71:
method MetricsMock.ReportDedupEvent already declared at pkg/metricsmanager/metrics_manager_mock.go:70:23

🪛 golangci-lint (2.12.2)

[error] 71-71: : # github.com/kubescape/node-agent/pkg/metricsmanager
pkg/metricsmanager/metrics_manager_mock.go:71:23: method MetricsMock.ReportDedupEvent already declared at pkg/metricsmanager/metrics_manager_mock.go:70:23

(typecheck)

🤖 Prompt for 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.

In `@pkg/metricsmanager/metrics_manager_mock.go` around lines 70 - 71, Remove the
duplicate MetricsMock.ReportDedupEvent method declaration, keeping one
implementation with the existing signature so the package compiles.

Source: Linters/SAST tools

Signed-off-by: entlein <einentlein@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: WIP

Development

Successfully merging this pull request may close these issues.

2 participants