From d8176dad1d1e2b0f6b67281059897a3090f6f7d6 Mon Sep 17 00:00:00 2001 From: Daniels Nagornuks Date: Wed, 10 Jun 2026 17:51:02 +0100 Subject: [PATCH] Docs: update demos to use AgentRuntime to enroll agents/tools instead of manual labeling Remove kagenti.io/type labels in deployment files and include AgentRuntime where necessary. Add a step to mTLS makefile to wait for labels to apply to workload deployment. Update documentation and comments to reflect the new behavior. Signed-off-by: Daniels Nagornuks --- authbridge/demos/echo/k8s/agent.yaml | 7 ++---- authbridge/demos/finance-sparc/k8s/agent.yaml | 7 ++---- .../k8s/git-issue-agent-deployment.yaml | 25 ++++++++++++++++--- .../k8s/github-tool-deployment.yaml | 4 +-- authbridge/demos/ibac/k8s/agent.yaml | 7 ++---- authbridge/demos/mtls/Makefile | 19 ++++++++++++++ authbridge/demos/mtls/README.md | 9 ++++--- authbridge/demos/mtls/k8s/callee.yaml | 19 +++++++++++--- authbridge/demos/mtls/k8s/caller.yaml | 19 +++++++++++--- .../demos/weather-agent/demo-ui-advanced.md | 14 ++++++----- .../k8s/weather-service-advanced.yaml | 1 - .../k8s/weather-tool-advanced.yaml | 14 +++++------ 12 files changed, 101 insertions(+), 44 deletions(-) diff --git a/authbridge/demos/echo/k8s/agent.yaml b/authbridge/demos/echo/k8s/agent.yaml index 4eba26e17..a53efb600 100644 --- a/authbridge/demos/echo/k8s/agent.yaml +++ b/authbridge/demos/echo/k8s/agent.yaml @@ -1,7 +1,6 @@ # Echo demo agent — operator-injected, kagenti-UI-discoverable. # # Required labels for this agent to show up in the kagenti UI: -# kagenti.io/type: agent — UI lists agents by this label # kagenti.io/inject: enabled — operator's webhook injects authbridge # kagenti.io/spire: disabled — demo doesn't depend on SPIRE identity # protocol.kagenti.io/a2a: "" — declares A2A protocol for the chat flow @@ -35,10 +34,9 @@ metadata: labels: app.kubernetes.io/name: echo-agent app.kubernetes.io/part-of: echo-demo - # Operator's AgentCardReconciler reads these from the workload + # Operator's AgentCardReconciler reads this from the workload # (not the Pod template) to know what protocol to fetch the card # under and to confirm the workload is an agent. - kagenti.io/type: agent protocol.kagenti.io/a2a: "" spec: replicas: 1 @@ -50,7 +48,6 @@ spec: labels: app.kubernetes.io/name: echo-agent app.kubernetes.io/part-of: echo-demo - kagenti.io/type: agent kagenti.io/inject: enabled kagenti.io/spire: disabled protocol.kagenti.io/a2a: "" @@ -145,7 +142,7 @@ spec: # We intentionally do NOT define an AgentCard CR here — the operator # auto-creates `-deployment-card` for any Deployment with -# `kagenti.io/type=agent`. Defining our own collides with the +# the corresponding AgentRuntime CR. Defining our own collides with the # admission webhook ("an AgentCard already targets Deployment X"). # The operator's CR points at the same Deployment, fetches our # `/.well-known/agent-card.json` endpoint, and stuffs the result diff --git a/authbridge/demos/finance-sparc/k8s/agent.yaml b/authbridge/demos/finance-sparc/k8s/agent.yaml index 59f62b567..740768056 100644 --- a/authbridge/demos/finance-sparc/k8s/agent.yaml +++ b/authbridge/demos/finance-sparc/k8s/agent.yaml @@ -1,7 +1,6 @@ # finance-sparc demo agent — operator-injected, kagenti-UI-discoverable. # # Required labels (same contract as demos/ibac/k8s/agent.yaml): -# kagenti.io/type: agent — UI lists agents by this label # kagenti.io/inject: enabled — operator's webhook injects authbridge # kagenti.io/spire: disabled — demo doesn't depend on SPIRE identity # protocol.kagenti.io/a2a: "" — declares A2A for the chat flow @@ -25,7 +24,6 @@ metadata: labels: app.kubernetes.io/name: finance-agent app.kubernetes.io/part-of: finance-sparc-demo - kagenti.io/type: agent protocol.kagenti.io/a2a: "" spec: replicas: 1 @@ -37,7 +35,6 @@ spec: labels: app.kubernetes.io/name: finance-agent app.kubernetes.io/part-of: finance-sparc-demo - kagenti.io/type: agent kagenti.io/inject: enabled kagenti.io/spire: disabled protocol.kagenti.io/a2a: "" @@ -106,8 +103,8 @@ spec: type: ClusterIP --- # AgentRuntime triggers operator-managed Keycloak client registration -# (the Secret the authbridge sidecar mounts at /shared/). The operator -# auto-creates the AgentCard CR for the kagenti.io/type=agent Deployment. +# (the Secret the authbridge sidecar mounts at /shared/) and auto-creation +# of an AgentCard CR for the agent deployment. apiVersion: agent.kagenti.dev/v1alpha1 kind: AgentRuntime metadata: diff --git a/authbridge/demos/github-issue/k8s/git-issue-agent-deployment.yaml b/authbridge/demos/github-issue/k8s/git-issue-agent-deployment.yaml index c40901948..93d6bed67 100644 --- a/authbridge/demos/github-issue/k8s/git-issue-agent-deployment.yaml +++ b/authbridge/demos/github-issue/k8s/git-issue-agent-deployment.yaml @@ -21,11 +21,13 @@ # - Calls the GitHub MCP tool via HTTP # - Token exchange to the tool is handled transparently by the AuthBridge sidecar # -# Labels (must be on Pod template): -# kagenti.io/type: agent - Required: identifies this as an agent workload +# Labels (on Pod template): # kagenti.io/inject: enabled - Enables AuthBridge sidecar injection # kagenti.io/spire: enabled - Enables SPIRE-based identity (set to "disabled" if no SPIRE) # +# kagenti.io/type is applied automatically by the operator when the +# AgentRuntime CR (at the end of this file) is created. +# # Usage: # kubectl apply -f git-issue-agent-deployment.yaml @@ -53,7 +55,6 @@ spec: metadata: labels: app.kubernetes.io/name: git-issue-agent - kagenti.io/type: agent kagenti.io/inject: enabled kagenti.io/spire: enabled spec: @@ -140,3 +141,21 @@ spec: targetPort: 8000 protocol: TCP type: ClusterIP + +--- +# AgentRuntime triggers operator-managed Keycloak client registration +# (creates kagenti-keycloak-client-credentials- Secret with +# client-id.txt and client-secret.txt that the authbridge sidecar +# mounts at /shared/). The operator also applies kagenti.io/type=agent +# to the Deployment and Pod template, enabling webhook sidecar injection. +apiVersion: agent.kagenti.dev/v1alpha1 +kind: AgentRuntime +metadata: + name: git-issue-agent + namespace: team1 +spec: + type: agent + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: git-issue-agent diff --git a/authbridge/demos/github-issue/k8s/github-tool-deployment.yaml b/authbridge/demos/github-issue/k8s/github-tool-deployment.yaml index ca64fe66a..181af6ee6 100644 --- a/authbridge/demos/github-issue/k8s/github-tool-deployment.yaml +++ b/authbridge/demos/github-issue/k8s/github-tool-deployment.yaml @@ -1,8 +1,8 @@ # GitHub Tool (MCP Server) Deployment # # This is the GitHub MCP tool that provides GitHub API access. -# It does NOT have AuthBridge injection — the kagenti.io/type label is omitted -# so the webhook pre-filter skips it entirely. +# It does NOT have AuthBridge injection — there is no AgentRuntime CR +# for this workload, so the operator does not enroll it. # # The tool validates incoming tokens to determine authorization level: # - Tokens with "github-full-access" scope → uses PRIVILEGED_ACCESS_PAT diff --git a/authbridge/demos/ibac/k8s/agent.yaml b/authbridge/demos/ibac/k8s/agent.yaml index 7c78a0dfa..05d840a03 100644 --- a/authbridge/demos/ibac/k8s/agent.yaml +++ b/authbridge/demos/ibac/k8s/agent.yaml @@ -1,7 +1,6 @@ # IBAC demo email-assistant agent — operator-injected, kagenti-UI-discoverable. # # Required labels for this agent to show up in the kagenti UI: -# kagenti.io/type: agent — UI lists agents by this label # kagenti.io/inject: enabled — operator's webhook injects authbridge # kagenti.io/spire: disabled — demo doesn't depend on SPIRE identity # protocol.kagenti.io/a2a: "" — declares A2A protocol for the chat flow @@ -33,10 +32,9 @@ metadata: labels: app.kubernetes.io/name: email-agent app.kubernetes.io/part-of: ibac-demo - # Operator's AgentCardReconciler reads these from the workload + # Operator's AgentCardReconciler reads this from the workload # (not the Pod template) to know what protocol to fetch the card # under and to confirm the workload is an agent. - kagenti.io/type: agent protocol.kagenti.io/a2a: "" spec: replicas: 1 @@ -48,7 +46,6 @@ spec: labels: app.kubernetes.io/name: email-agent app.kubernetes.io/part-of: ibac-demo - kagenti.io/type: agent kagenti.io/inject: enabled kagenti.io/spire: disabled protocol.kagenti.io/a2a: "" @@ -147,7 +144,7 @@ spec: # We intentionally do NOT define an AgentCard CR here — the operator # auto-creates `-deployment-card` for any Deployment with -# `kagenti.io/type=agent`. Defining our own collides with the +# the corresponding AgentRuntime CR. Defining our own collides with the # admission webhook ("an AgentCard already targets Deployment X"). # The operator's CR points at the same Deployment, fetches our # `/.well-known/agent-card.json` endpoint, and stuffs the result diff --git a/authbridge/demos/mtls/Makefile b/authbridge/demos/mtls/Makefile index 4cee6c13e..14fd091ca 100644 --- a/authbridge/demos/mtls/Makefile +++ b/authbridge/demos/mtls/Makefile @@ -10,10 +10,28 @@ CALLEE_ENVOY ?= mtls-callee-envoy help: @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[36m%-32s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) +.PHONY: wait-operator-labels +wait-operator-labels: ## Wait for the operator to apply kagenti.io/type from AgentRuntime CRs + @for deploy in $(CALLER) $(CALLEE); do \ + echo "[*] Waiting for operator to label $$deploy ..."; \ + for i in $$(seq 1 60); do \ + val=$$(kubectl -n $(NAMESPACE) get deploy/$$deploy \ + -o jsonpath="{.spec.template.metadata.labels['kagenti.io/type']}" 2>/dev/null); \ + if [ "$$val" = "agent" ]; then echo " ✓ $$deploy labeled"; break; fi; \ + sleep 2; \ + done; \ + val=$$(kubectl -n $(NAMESPACE) get deploy/$$deploy \ + -o jsonpath="{.spec.template.metadata.labels['kagenti.io/type']}" 2>/dev/null); \ + if [ "$$val" != "agent" ]; then \ + echo "ERROR: operator did not label $$deploy within 120s"; exit 1; \ + fi; \ + done + .PHONY: deploy deploy: ## Deploy caller + callee pods (both with mtls.mode: strict) kubectl apply -f k8s/caller.yaml kubectl apply -f k8s/callee.yaml + $(MAKE) wait-operator-labels ./scripts/patch-mtls-config.sh $(NAMESPACE) strict kubectl -n $(NAMESPACE) rollout restart deploy/$(CALLER) deploy/$(CALLEE) kubectl -n $(NAMESPACE) rollout status deploy/$(CALLER) deploy/$(CALLEE) --timeout=120s @@ -26,6 +44,7 @@ demo-mtls: deploy ## Deploy + run the encryption-on-the-wire check (strict mode) demo-mtls-permissive: ## Same shape but with mtls.mode: permissive; verify mixed-mode serving kubectl apply -f k8s/caller.yaml kubectl apply -f k8s/callee.yaml + $(MAKE) wait-operator-labels ./scripts/patch-mtls-config.sh $(NAMESPACE) permissive kubectl -n $(NAMESPACE) rollout restart deploy/$(CALLER) deploy/$(CALLEE) kubectl -n $(NAMESPACE) rollout status deploy/$(CALLER) deploy/$(CALLEE) --timeout=120s diff --git a/authbridge/demos/mtls/README.md b/authbridge/demos/mtls/README.md index 3eaf78c79..f9554becd 100644 --- a/authbridge/demos/mtls/README.md +++ b/authbridge/demos/mtls/README.md @@ -251,11 +251,12 @@ cleanly for envoy-sidecar too. The namespace-CM patch this demo uses is a lighter-weight knob that avoids requiring a CR for every demo pod. -### Demo pod manifests need `kagenti.io/type: agent` +### Demo pod enrollment via AgentRuntime The kagenti-operator's mutating-webhook `objectSelector` requires -`kagenti.io/type` in `[agent, tool]`. The demo Pod templates now set -this label and an explicit `runAsUser: 100` on the `demo-app` -container (the `curlimages/curl:latest` image runs as the +`kagenti.io/type` in `[agent, tool]`. The demo manifests include +AgentRuntime CRs that enroll each workload — the operator applies +the label automatically. The `demo-app` containers set an explicit +`runAsUser: 100` (the `curlimages/curl:latest` image runs as the non-numeric `curl_user`, which trips the kubelet's `runAsNonRoot` check without an explicit numeric UID). diff --git a/authbridge/demos/mtls/k8s/callee.yaml b/authbridge/demos/mtls/k8s/callee.yaml index 072756d5f..4dae6b411 100644 --- a/authbridge/demos/mtls/k8s/callee.yaml +++ b/authbridge/demos/mtls/k8s/callee.yaml @@ -33,9 +33,6 @@ spec: app.kubernetes.io/part-of: mtls-demo kagenti.io/inject: enabled kagenti.io/spire: enabled - # Required by the kagenti-operator's mutating-webhook - # objectSelector ({kagenti.io/type: [agent, tool]}). - kagenti.io/type: agent spec: serviceAccountName: mtls-callee securityContext: @@ -89,3 +86,19 @@ spec: protocol: TCP name: http type: ClusterIP + +--- +# AgentRuntime enrolls this workload with the operator. The operator +# applies kagenti.io/type=agent to the Deployment and Pod template, +# which triggers webhook sidecar injection and Keycloak client registration. +apiVersion: agent.kagenti.dev/v1alpha1 +kind: AgentRuntime +metadata: + name: mtls-callee + namespace: team1 +spec: + type: agent + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: mtls-callee diff --git a/authbridge/demos/mtls/k8s/caller.yaml b/authbridge/demos/mtls/k8s/caller.yaml index 6ea32ab35..e4600077d 100644 --- a/authbridge/demos/mtls/k8s/caller.yaml +++ b/authbridge/demos/mtls/k8s/caller.yaml @@ -36,9 +36,6 @@ spec: app.kubernetes.io/part-of: mtls-demo kagenti.io/inject: enabled kagenti.io/spire: enabled - # Required by the kagenti-operator's mutating-webhook - # objectSelector ({kagenti.io/type: [agent, tool]}). - kagenti.io/type: agent spec: serviceAccountName: mtls-caller securityContext: @@ -96,3 +93,19 @@ spec: protocol: TCP name: http type: ClusterIP + +--- +# AgentRuntime enrolls this workload with the operator. The operator +# applies kagenti.io/type=agent to the Deployment and Pod template, +# which triggers webhook sidecar injection and Keycloak client registration. +apiVersion: agent.kagenti.dev/v1alpha1 +kind: AgentRuntime +metadata: + name: mtls-caller + namespace: team1 +spec: + type: agent + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: mtls-caller diff --git a/authbridge/demos/weather-agent/demo-ui-advanced.md b/authbridge/demos/weather-agent/demo-ui-advanced.md index 59efa6680..c656ad3a3 100644 --- a/authbridge/demos/weather-agent/demo-ui-advanced.md +++ b/authbridge/demos/weather-agent/demo-ui-advanced.md @@ -183,11 +183,11 @@ python demos/weather-agent/setup_keycloak_weather_advanced.py -n team1 > **Expected catalog quirk.** The **Agent Catalog** shows **two** entries: > `weather-service-advanced` *and* `weather-tool-advanced`. The **Tool -> Catalog** is empty. This is by design — the advanced demo labels the -> tool with `kagenti.io/type: agent` so AuthBridge gets injected on it -> (the `injectTools` feature gate is off by default; see the -> [kubectl appendix](#operator-gotchas)). Pick `weather-service-advanced` -> for chat. +> Catalog** is empty. This is by design — the tool's AgentRuntime CR +> uses `type: agent` so the operator applies `kagenti.io/type=agent` +> and AuthBridge gets injected (the `injectTools` feature gate is off +> by default; see the [kubectl appendix](#operator-gotchas)). Pick +> `weather-service-advanced` for chat. 1. **Agent Catalog** → namespace `team1` → `weather-service-advanced` → **View Details**. The agent card should render (proves the agent is up and @@ -302,12 +302,14 @@ If you'd rather skip the UI entirely, the same demo runs via raw manifests. python authbridge/demos/weather-agent/setup_keycloak_weather_advanced.py \ -n team1 --wait-tool-client & -# 2. Apply manifests (the deploy script applies AgentRuntime CRs too) +# 2. Apply manifests kubectl apply -f authbridge/demos/weather-agent/k8s/configmaps-advanced.yaml kubectl apply -f authbridge/demos/weather-agent/k8s/weather-tool-advanced.yaml +kubectl apply -f authbridge/demos/weather-agent/k8s/agentruntime-weather-tool-advanced.yaml kubectl rollout status deploy/weather-tool-advanced -n team1 --timeout=300s kubectl apply -f authbridge/demos/weather-agent/k8s/weather-service-advanced.yaml +kubectl apply -f authbridge/demos/weather-agent/k8s/agentruntime-weather-service-advanced.yaml kubectl rollout status deploy/weather-service-advanced -n team1 --timeout=420s # 3. Re-run Keycloak setup so the agent client gets the optional exchange scope diff --git a/authbridge/demos/weather-agent/k8s/weather-service-advanced.yaml b/authbridge/demos/weather-agent/k8s/weather-service-advanced.yaml index c34e7d001..2b7026569 100644 --- a/authbridge/demos/weather-agent/k8s/weather-service-advanced.yaml +++ b/authbridge/demos/weather-agent/k8s/weather-service-advanced.yaml @@ -32,7 +32,6 @@ spec: template: metadata: labels: - kagenti.io/type: agent protocol.kagenti.io/a2a: "" app.kubernetes.io/name: weather-service-advanced kagenti.io/inject: enabled diff --git a/authbridge/demos/weather-agent/k8s/weather-tool-advanced.yaml b/authbridge/demos/weather-agent/k8s/weather-tool-advanced.yaml index 9e5c692c8..d0f7e0b98 100644 --- a/authbridge/demos/weather-agent/k8s/weather-tool-advanced.yaml +++ b/authbridge/demos/weather-agent/k8s/weather-tool-advanced.yaml @@ -6,11 +6,13 @@ # The MCP server listens on port 8000. AuthBridge ext_proc listens on 9090 # inside the pod, so there is no port collision (unlike the GitHub tool on 9090). # -# The kagenti AuthBridge mutating webhook only injects sidecars for pod -# labels kagenti.io/type: agent (see kagenti-operator/internal/webhook/ -# in the kagenti-operator repo for the matching rule). This workload is -# still the weather MCP tool by image and name; the pod is labeled as agent so -# client-registration runs and registers the tool SPIFFE in Keycloak. +# The kagenti-operator's AgentRuntime CR (agentruntime-weather-tool-advanced.yaml) +# enrolls this workload and triggers AuthBridge sidecar injection. The +# AgentRuntime uses type "agent" so the operator applies kagenti.io/type=agent +# to the pod (the injectTools feature gate is off by default). This workload +# is still the weather MCP tool by image and Deployment name; it is enrolled +# as type "agent" so client-registration runs and registers the tool's +# SPIFFE identity in Keycloak. # # Usage: # kubectl apply -f weather-tool-advanced.yaml @@ -46,8 +48,6 @@ spec: template: metadata: labels: - # Required for AuthBridge + SPIRE injection (webhook does not match type=tool) - kagenti.io/type: agent app.kubernetes.io/name: weather-tool-advanced app.kubernetes.io/component: mcp-tool protocol.kagenti.io/mcp: ""