Skip to content

Feat: add ValidatingAdmissionPolicy to protect kagenti.io/type label#418

Merged
pdettori merged 4 commits into
rossoctl:mainfrom
rh-dnagornuks:vap-agent-label
Jun 23, 2026
Merged

Feat: add ValidatingAdmissionPolicy to protect kagenti.io/type label#418
pdettori merged 4 commits into
rossoctl:mainfrom
rh-dnagornuks:vap-agent-label

Conversation

@rh-dnagornuks

@rh-dnagornuks rh-dnagornuks commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Prerequisites

This PR can be reviewed in parallel but should not be merged until the following PRs are merged due to breaking changes:

Summary

The kagenti.io/type label on Deployments and StatefulSets can currently be applied by any user, bypassing the AgentRuntime workflow. This PR adds a ValidatingAdmissionPolicy (VAP) that prevents direct application of the label in both the workload metadata and pod template spec — only the operator's service account (via the AgentRuntime controller) can set or change it. This enforces the intended enrollment flow: users create an AgentRuntime CR, and the controller applies the label. The DefaultsConfigReconciler (which handled unmanaged labeled workloads) is removed since the VAP makes that path impossible. Documentation, samples, and E2E tests are updated to reflect the new enforcement.

Changes

  • ValidatingAdmissionPolicy + Binding — rejects CREATE/UPDATE of Deployments/StatefulSets that set kagenti.io/type in either metadata.labels or spec.template.metadata.labels, unless the request is from the operator's service account or the label value is unchanged on an UPDATE
  • Kustomize + Helm chart templates — both deployment paths (kustomize via config/vap/ and Helm via charts/kagenti-operator/templates/vap/) apply the policy on install
  • E2E test refactoring — fixtures no longer manually apply kagenti.io/type; tests create an AgentRuntime CR and wait for the operator to apply the label before proceeding
  • DefaultsConfigReconciler removal — deleted the reconciler, its tests, and the ComputeDefaultsOnlyHash function (no longer reachable with VAP in place)
  • Documentation updates — removed manual labeling instructions from GETTING_STARTED.md, api-reference.md, dynamic-agent-discovery.md, controller-webhook-interaction.md, operator-managed-client-registration.md; all examples now use AgentRuntime CRs; "Defaults-Only Path" section replaced with "AgentRuntime Required — Admission Policy"
  • Sample fix — removed manual kagenti.io/type from skill-discovery sample Deployment metadata
  • AgentCard naming fix — updated api-reference.md to use the auto-generated {name}-{kind}-card naming pattern
  • Stale comment cleanup — removed references to deleted defaults_config_reconciler.go in controller and fixture comments

Testing

  • Pre-commit checks/tests pass
  • Admission Policy correct behavior verified on a ROSA cluster — manual labeling blocked, operator label application succeeds
  • Kagenti UI creates workloads successfully

Related Issues

Resolves: RHAIENG-4934
Resolves: RHAIENG-4937

Made with Cursor

Prevents manual labeling of Deployments and StatefulSets with
kagenti.io/type in both metadata and pod template spec. Only the
operator SA may set this label via an AgentRuntime CR.

Includes both kustomize (config/vap/) and Helm chart templates.

Signed-off-by: Daniels Nagornuks <dnagornu@redhat.com>

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Removes manual kagenti.io/type labels from all E2E test fixtures
(echoAgent, noProtoAgent, auditAgent, signedAgent, authBridgeAgent,
authBridgeDisabledAgent) and adds AgentRuntime CRs to drive the label
via the operator. Each test now deploys the workload without the
protected label, creates an AgentRuntime, waits for the operator to
apply kagenti.io/type, then proceeds with existing assertions. This
aligns E2E tests with the new ValidatingAdmissionPolicy that prevents
manual label application.

Signed-off-by: Daniels Nagornuks <dnagornu@redhat.com>

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
The DefaultsConfigReconciler maintained config-hash annotations on
workloads with kagenti.io/type that were not managed by an AgentRuntime
CR. With the new ValidatingAdmissionPolicy enforcing that the label can
only be set via an AgentRuntime, no unmanaged workloads can exist.
The reconciler was effectively a no-op and is now removed along with
its tests and the ComputeDefaultsOnlyHash helper.

Signed-off-by: Daniels Nagornuks <dnagornu@redhat.com>

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Remove manual kagenti.io/type labeling from docs, samples, and
comments. All examples now use AgentRuntime CRs, matching the
ValidatingAdmissionPolicy that prevents direct label application.
Fix auto-created AgentCard naming to use the {name}-{kind}-card
pattern produced by AgentCardSync.

Signed-off-by: Daniels Nagornuks <dnagornu@redhat.com>

@pdettori pdettori left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Clean, well-scoped security hardening. The VAP correctly enforces operator-only application of kagenti.io/type, the Helm/kustomize SA parity is handled (Helm templates the SA + exemptServiceAccounts; kustomize hardcodes both default identities with an explanatory comment), and tests/docs are updated to match the AgentRuntime-driven flow. The DefaultsConfigReconciler removal compiles cleanly (Build/Unit/Integration green = no dangling references), and the E2E refactor uses Gomega Eventually polling for the operator-applied label rather than manual labeling — no skips, no naked sleeps.

One minor CEL-robustness suggestion inline; no blocking issues.

Non-blocking note: the policy is enabled by default and is a breaking change — already flagged by the author with dependent PRs (kagenti#1907, kagenti-extensions#494) as merge prerequisites.

Areas reviewed: Helm/K8s (CEL/VAP), Go (controller, main.go), E2E tests, Docs, Kustomize. Commits: 4, all signed-off. CI: all 16 checks passing.

Assisted-By: Claude Code

variables:
- name: metaLabelUnchanged
expression: >-
!('kagenti.io/type' in object.metadata.labels)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

object.metadata.labels is accessed here without a has() guard, unlike the has-agent-type-label matchCondition and the parallel podTemplateLabelUnchanged variable (both of which guard with has(...)). If a non-operator submits a workload carrying kagenti.io/type only in the pod template with no top-level metadata.labels, this expression accesses an absent map; under failurePolicy: Fail that risks an eval-error denial instead of the intended messageExpression. The outcome (deny) is still correct, so this is low severity, but suggest guarding for symmetry:

!has(object.metadata.labels)
|| !('kagenti.io/type' in object.metadata.labels)
|| (request.operation == 'UPDATE' && ...)

The same applies to the identical Helm template at charts/kagenti-operator/templates/vap/validating-admission-policy.yaml.

@pdettori
pdettori merged commit 46fcf27 into rossoctl:main Jun 23, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants