Skip to content

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

Merged
matthyx merged 47 commits into
kubescape:mainfrom
k8sstormcenter:migrate/sbob
Aug 18, 2026
Merged

chore: deprecate old profiletypes in favor of containerprofiles#864
matthyx merged 47 commits into
kubescape:mainfrom
k8sstormcenter:migrate/sbob

Conversation

@entlein

@entlein entlein commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Overview

Migrating old AP/NN/ug- structure to exclusively be container profile, migrating also all tests.
Benchmarks were run : CPU flat, mem somewhat better

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?

-> Decided to switch completely

Behavior that changes with this PR

ephemeral containers and initcontainers are now recorded differently.

TODO

Comment before merging

Discussion on ephemeral containers

  • full treatment added, tests partially flaky -> would be great to see the actual CTs run, to confirm

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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main migration from legacy profile types to ContainerProfiles.
✨ 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/network_fixture_lint_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.

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.

Addressed. The read path now derives a per-container slug and only falls back to the bare name:

  • containerprofilecache.go:344 perContainerName := overlayName + "-" + container.Runtime.ContainerName
  • fetched at :347, and only if that 404s does it fall back to the bare overlayName (:358).

So a sidecar resolves <overlay>-<sidecarName>, not the app container's CP. Covered end-to-end by TestUserDefinedCP_PerContainerBinding (containerprofilecache_test.go:399) — asserts each container resolves its own per-container CP — and TestUserDefinedCP_SingleContainerBareFallback for the fallback.

// 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.

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.

Addressed — migrated to CP form rather than dropped. The four you named now exist against the user-CP path:

  • fast-skip when CP RV and UserCPRV both match → TestRefreshUserCP_FastSkipWhenRVsMatch (containerprofilecache_test.go:595)
  • rebuild when only UserCPRV changes → TestRefreshUserCP_RebuildWhenUserCPRVChanges (:634)
  • rebuild on learned-CP RV change → TestRefreshRebuildsOnCPChange (reconciler_test.go:326)
  • the entry.RV freeze this exposed → TestRefreshReflectsAuthoredCPEdit_RVFreezeProof (containerprofilecache_test.go:512)

Plus TestRefreshPreservesEntryOnTransientUserCPError and TestRefreshUserCP_NoLearnedCP.

}
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.

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.

Addressed. A label-referenced CP that is actually a learned profile is now rejected before it can be forced to Completed/Full:

containerprofilecache.go:386-394 — if the fetched user CP carries the status annotation (i.e. it is learned, not authored), it is dropped (userDefinedCP = nil) with a Warning. Only an authored CP (no lifecycle annotations) is adopted.

@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
Comment thread pkg/objectcache/containerprofilecache/containerprofilecache.go Outdated
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.

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.

Reordered. The terminal-status gate now runs only when no authored CP will be used:

containerprofilecache.go:404 if userDefinedCP == nil && cp != nil && !isTerminalCPStatus(...). The authored CP is fetched first (:347/:358), so a mid-learning base CP no longer blocks adopting an authored profile that replaces it.

Comment thread pkg/objectcache/containerprofilecache/containerprofilecache.go Outdated
Comment thread pkg/objectcache/containerprofilecache/containerprofilecache_test.go
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.

Comment thread tests/component_test.go
assert.Equal(t, 0, countR0001("app", "id"),
"id IS in the app section — R0001 in app means the wrong section was enforced")

// KNOWN LIMITATION — ephemeral container runtime tracing. The debug

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

AI: I thought you solved it, do you start these eph containers with sharedpid-ns or without?
Get a fresh PG and add a CT section on learning the eph/init section (no user-defined labels at all), then use the exact learned schema for the detection.
See above -> maybe there is still a bug in the identation of the whole yaml?

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.

Answered in a PR-level comment with the full evidence: ephemeral containers are neither learned nor alerted on. The learning manager skips them (ignoring ephemeral container, the guard from #579), while the rule manager still monitors them and then finds no profile (ContainerProfile not yet available ... -ephcon-... not found). Measured on k3s: no ContainerProfile for the eph container and 0 alerts on repeated exec ... -c ephcon -- id.

On the shared-PID-ns question: there is no shareProcessNamespace handling in the code either way. So no dedicated CT here yet — the fix is upstream (remove/relax the guard, or enforce the target container's profile), and the CT for eph learn+alert should follow that.

@entlein entlein left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

AI: please go through the CTs

  • restore the learning tests (while keeping the detection tests)
  • there are inconsistencies with the overall profile multi-container sections
  • those may or may not be the reason why the debug containers dont alert.

…nce when ephermal continers are not being learnt anymore

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

Copy link
Copy Markdown
Contributor

Ephemeral containers are neither learned nor alerted on

Origin. Introduced by PR #579 "Refactor profiles creation" (#579), merged 2025-07-10 (merge commit 7abdbecf; the guard commit bdc79cc6 is dated 2025-06-16). It is still live in main today — the // Ignore ephemeral containers block in pkg/containerprofilemanager/v1/lifecycle.go.

Behaviour (verified on k3s, kernel 6.1, this branch's images). The two subsystems disagree on ephemeral containers:

  • The learning manager skips them (the guard): "ignoring ephemeral container" ... containerName: ephcon → no ContainerProfile is ever written.
  • The rule manager still monitors them: "RuleManager - start monitor on container ... ephcon", then "ContainerProfile not yet available ... replicaset-<wl>-ephcon-<...> not found".

So an ephemeral container is watched for events but has no profile, and every profile-dependent rule (R0001, R0003, R0004, …) needs a completed profile to evaluate. Result, measured:

  • kubectl get containerprofiles for the ephemeral container → none (not learned).
  • Repeated kubectl exec <pod> -c ephcon -- /usr/bin/id0 alerts (R0001/R0040 count 0, no alert lines reference the ephemeral container).

Net: a process run inside an injected/debug ephemeral container (kubectl debug, or a crafted ephemeralContainers entry) executes with zero runtime detection.

Fix size. Small on the code side — the rule path is already wired (it monitors the ephemeral container and looks up a …-ephcon-… profile), so only the learning side needs to stop skipping:

  • Minimal: remove the ~15-line guard in lifecycle.go so ephemeral containers flow through the existing learning path; the rule manager then finds the profile and alerts. One file, ~15 LoC.
  • The design decision that turns this from XS into S–M is short-lived ephemeral containers: a debug session may exit before the learning window completes, leaving no profile during its life. Options: partial-profile generation for ephemeral containers; or have the ephemeral container enforce the target container's profile (they share the pod, often the PID namespace), so any process not in the target's profile alerts immediately with no learning delay; or fail-closed (alert when an ephemeral container has no profile — a debug container is itself the anomaly). Plus a component test pinning learn + alert for ephemeral containers.

@entlein

entlein commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Ephemeral containers are neither learned nor alerted on

Origin. Introduced by PR #579 "Refactor profiles creation" (#579), merged 2025-07-10 (merge commit 7abdbecf; the guard commit bdc79cc6 is dated 2025-06-16). It is still live in main today — the // Ignore ephemeral containers block in pkg/containerprofilemanager/v1/lifecycle.go.

Behaviour (verified on k3s, kernel 6.1, this branch's images). The two subsystems disagree on ephemeral containers:

  • The learning manager skips them (the guard): "ignoring ephemeral container" ... containerName: ephcon → no ContainerProfile is ever written.
  • The rule manager still monitors them: "RuleManager - start monitor on container ... ephcon", then "ContainerProfile not yet available ... replicaset-<wl>-ephcon-<...> not found".

So an ephemeral container is watched for events but has no profile, and every profile-dependent rule (R0001, R0003, R0004, …) needs a completed profile to evaluate. Result, measured:

  • kubectl get containerprofiles for the ephemeral container → none (not learned).
  • Repeated kubectl exec <pod> -c ephcon -- /usr/bin/id0 alerts (R0001/R0040 count 0, no alert lines reference the ephemeral container).

Net: a process run inside an injected/debug ephemeral container (kubectl debug, or a crafted ephemeralContainers entry) executes with zero runtime detection.

Fix size. Small on the code side — the rule path is already wired (it monitors the ephemeral container and looks up a …-ephcon-… profile), so only the learning side needs to stop skipping:

  • Minimal: remove the ~15-line guard in lifecycle.go so ephemeral containers flow through the existing learning path; the rule manager then finds the profile and alerts. One file, ~15 LoC.
  • The design decision that turns this from XS into S–M is short-lived ephemeral containers: a debug session may exit before the learning window completes, leaving no profile during its life. Options: partial-profile generation for ephemeral containers; or have the ephemeral container enforce the target container's profile (they share the pod, often the PID namespace), so any process not in the target's profile alerts immediately with no learning delay; or fail-closed (alert when an ephemeral container has no profile — a debug container is itself the anomaly). Plus a component test pinning learn + alert for ephemeral containers.

@matthyx : do you remember the rational behind excl ephemeral containers from learning/alerting? I d prefer we have this consistent -> either they are ignored (then they are not in the spec) or they get the full treatment 💊🤓 . I think, I m missing the content. We can also fix this later ... it s doesnt seem to be a major use-case.

@matthyx

matthyx commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

let's give them the full treatment, please

… due to timeouts

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

entlein commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

CT 27 is supposed to fail for this PR (that s the missing opens-gadget redis test)
13 is having timeout issues -> pushed a small fix
19 is flaky

Suggest: follow-up PR that investigates time-out, wait, sleep and other asserts that are not 100% deterministic at the moment.

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

entlein commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

@matthyx : if you have a second -> would you please kick off the CTs here on the PR so I can see, how much is failing here (our github workflows are not 100 percent identical).
Thank you 🙏

@matthyx

matthyx commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

@entlein check #897 (I think it's fine like that)

@matthyx matthyx moved this from WIP to Waiting on Author in KS PRs tracking Aug 17, 2026
Signed-off-by: entlein <einentlein@gmail.com>
Signed-off-by: entlein <einentlein@gmail.com>
Signed-off-by: entlein <einentlein@gmail.com>
Signed-off-by: entlein <einentlein@gmail.com>
@ConstanzeTU

Copy link
Copy Markdown
Contributor

Overlay component tests are green on the current migrate/sbob tip (fork CI, storage built from the matching fork tree): 46/47 legs pass.

Run: https://github.com/k8sstormcenter/node-agent/actions/runs/32068856082

  • The single red leg is Test_27_ApplicationProfileOpens, which depends on the fullpath-opens gadget change that is not on this branch — expected, not a regression.
  • Test_30_IgnoreExcludeAndLearningDuration is mildly flaky: its ExcludeFlagsDropContainer subtest waits a fixed interval before asserting the non-excluded control workload is profiled, which can be too short on a loaded runner. It passes on rerun. A clean hardening would poll for the control profile instead of a fixed sleep.

Coverage exercised this round includes: reconcile refresh handling of a deleted vs transiently-unavailable ContainerProfile (learned and authored), add/refresh parity on authored per-subtype resolution, ephemeral-container full treatment, per-subtype authored enforcement, and the grouped-document storage round-trip.

We did a further pass over the change set and the storage counterpart and found nothing else outstanding. From our side this is ready for review, or to merge.

@matthyx

… is set

refreshOneEntry's transient-error guards for the learned CP and the
authored (label-referenced) CP were gated on the entry already having a
previously cached ResourceVersion (e.RV != "" / e.UserCPRV != ""). That
gate meant a non-NotFound (transient) error on the *first-ever* fetch of
either source fell through instead of returning early, discarding the
"we don't actually know the true state" signal.

Concretely, for the authored side: an entry can be UserCPRef != nil with
UserCPRV == "" right after the overlay label is added, before the first
successful authored-CP fetch. If a transient error hits that fetch while,
in the same tick, the learned CP genuinely 404s, the function reached the
cp == nil && userDefinedCP == nil branch and permanently evicted the
entry (profile_deleted), even though the authored side only had a
transient blip. This silently dropped enforcement for the container until
the next Add event or pod restart.

The symmetric case exists on the learned-CP side too: an entry built
purely from an authored CP (learned CP never observed, e.RV == "") that
hits a transient learned-CP error while the authored CP genuinely 404s in
the same tick hit the same eviction path.

Fix: drop the e.RV != "" / e.UserCPRV != "" conditions from both guards.
A transient (non-NotFound) error on an attempted fetch now always keeps
the cached entry as-is and retries next tick, regardless of whether that
source had previously resolved successfully.

Added TestRefresh_AuthoredCP_TransientErrorBeforeFirstFetchKeepsEntry and
TestRefresh_LearnedCP_TransientErrorBeforeFirstFetchKeepsEntry, verified
to fail against the pre-fix code and pass with the fix.

Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
@matthyx
matthyx merged commit 28fe087 into kubescape:main Aug 18, 2026
29 of 33 checks passed
@matthyx
matthyx deleted the migrate/sbob branch August 18, 2026 08:21
matthyx added a commit that referenced this pull request Aug 18, 2026
…drift (#900)

Follow-up to #864, split from its review into two CI-hygiene fixes:

- component-tests.yaml matrix: Test_12/Test_13 referenced stale function
  names (Test_12_MergingProfilesTest, Test_13_MergingNetworkNeighborhoodTest)
  that no longer exist after the #864 migration renamed them; go test -run
  silently matches nothing and exits 0, so these entries were reporting
  green while running zero test code. Updated to the current function names
  (Test_12_AuthoredMultiSubtypeProfile, Test_13_NaturalLearningLifecycle).
  Also added real, matrix-shaped test functions that existed in
  tests/component_test.go but were never wired into the matrix:
  Test_25, Test_28, Test_30, Test_33, Test_35, Test_43, Test_49.
  Test_09_FalsePositiveTest was deliberately left out: it carries its own
  t.Skip with "Also removed from the CI matrix" pending a storage
  write-serialization fix, so re-adding it would be a no-op at best.

- tests/scripts/storage-tag.sh: it mirrors whatever storage image tag
  kubescape/helm-charts' main branch has pinned, which currently lags
  behind the github.com/kubescape/storage client library version pinned in
  this repo's go.mod (v0.0.303). The older server silently drops the
  grouped Containers/InitContainers/EphemeralContainers subtype fields the
  newer client writes, so Test_48_MultiSubtypeGroupedProfileDocument's
  round-trip would come back empty. The script now takes the max of the
  dynamically-fetched tag and this repo's go.mod-pinned floor
  (derived via `go list -m`), so CI doesn't silently regress again next
  time helm-charts lags. The durable fix is bumping helm-charts' own pin;
  that's a separate repo/PR.

Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
matthyx added a commit that referenced this pull request Aug 18, 2026
#864 renamed the ap.*/nn.* CEL rule-library helper namespaces to cp.* with
no backward-compatible aliases. Since CEL rules are loaded from CRDs at
runtime (user-authored detection rules), any pre-existing rule still
referencing ap.*/nn.* now fails to compile — and pkg/utils/cel.go silently
disables the expression on a compile failure (logs a warning, no error
surfaced), so affected rules just stop detecting anything.

Register the same 14 ap.* and 6 nn.* helper functions (e.g. ap.was_executed,
nn.is_domain_in_egress) alongside the existing cp.* namespace, wired to the
exact same Go implementations via a shared funcSpecs table per library, so
cp.* and its legacy alias can never drift apart. cel-go requires overload
ids to be unique per environment, so the alias registers its own overload
ids (ap_*/nn_*) rather than literally reusing cp.*'s FunctionOpt values —
only the CEL-facing glue is duplicated, not the detection logic.

These aliases are a deliberate transition/deprecation window, not a
permanent API; both AP()/NN() and the CEL registration in cel.go are
commented accordingly for removal once user rules have migrated to cp.*.

Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>

Docs-exempt: internal CEL rule-library namespace change; #864 (the PR that
introduced the cp.* rename this follows up on) made no docs/ changes either,
and no doc in this repo documents the ap./nn./cp. helper namespaces. The
change is self-documented via code comments on AP()/NN() and in this PR
description.

Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
matthyx added a commit that referenced this pull request Aug 18, 2026
…ty (#901)

#864 renamed the ap.*/nn.* CEL rule-library helper namespaces to cp.* with
no backward-compatible aliases. Since CEL rules are loaded from CRDs at
runtime (user-authored detection rules), any pre-existing rule still
referencing ap.*/nn.* now fails to compile — and pkg/utils/cel.go silently
disables the expression on a compile failure (logs a warning, no error
surfaced), so affected rules just stop detecting anything.

Register the same 14 ap.* and 6 nn.* helper functions (e.g. ap.was_executed,
nn.is_domain_in_egress) alongside the existing cp.* namespace, wired to the
exact same Go implementations via a shared funcSpecs table per library, so
cp.* and its legacy alias can never drift apart. cel-go requires overload
ids to be unique per environment, so the alias registers its own overload
ids (ap_*/nn_*) rather than literally reusing cp.*'s FunctionOpt values —
only the CEL-facing glue is duplicated, not the detection logic.

These aliases are a deliberate transition/deprecation window, not a
permanent API; both AP()/NN() and the CEL registration in cel.go are
commented accordingly for removal once user rules have migrated to cp.*.



Docs-exempt: internal CEL rule-library namespace change; #864 (the PR that
introduced the cp.* rename this follows up on) made no docs/ changes either,
and no doc in this repo documents the ap./nn./cp. helper namespaces. The
change is self-documented via code comments on AP()/NN() and in this PR
description.

Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
@matthyx matthyx moved this from Waiting on Author to To Archive in KS PRs tracking Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: To Archive

Development

Successfully merging this pull request may close these issues.

3 participants