From 9058c746c5e3464538a61806a0963589787a92d1 Mon Sep 17 00:00:00 2001 From: Daniels Nagornuks Date: Thu, 7 May 2026 12:33:45 +0100 Subject: [PATCH] docs: update SPIRE signing demo for Kind and OpenShift Document Kind/SPIRE setup and Helm-based signature verification configuration, including OpenShift-specific requirements such as SCC permissions, namespace labels, and trust bundle key overrides. Remove the static AgentCard manifest and rely on the operator-generated AgentCard (weather-agent-deployment-card). Update demo and teardown scripts, and add troubleshooting guidance for image pull rate limits and building the agentcard-signer image. Signed-off-by: Daniels Nagornuks --- .../demos/agentcard-spire-signing/demo.md | 111 +++++++++++++----- .../k8s/agentcard.yaml | 13 -- .../run-demo-commands.sh | 6 +- .../agentcard-spire-signing/teardown-demo.sh | 1 - 4 files changed, 84 insertions(+), 47 deletions(-) delete mode 100644 kagenti-operator/demos/agentcard-spire-signing/k8s/agentcard.yaml diff --git a/kagenti-operator/demos/agentcard-spire-signing/demo.md b/kagenti-operator/demos/agentcard-spire-signing/demo.md index c21f9cf3..e6cb7738 100644 --- a/kagenti-operator/demos/agentcard-spire-signing/demo.md +++ b/kagenti-operator/demos/agentcard-spire-signing/demo.md @@ -27,61 +27,85 @@ The operator verifies the JWS signature using the x5c certificate chain embedded ## Prerequisites -- Kubernetes cluster with SPIRE installed (e.g. `kagenti/deployments/run_install.sh --env dev`) -- `spire-controller-manager` running (for ClusterSPIFFEID support) -- SPIFFE CSI driver available (`csi.spiffe.io`) -- Trust bundle ConfigMap in the cluster (e.g. `spire-bundle` in `spire-system`) -- kagenti-operator deployed with signature verification flags (see step 2 below) +- Kubernetes cluster with SPIRE installed (e.g. `kagenti/scripts/kind/setup-kagenti.sh --with-spire`) +- kagenti-operator deployed with the following signature verification flags: -## Setup +```bash +--require-a2a-signature=true +--enforce-network-policies=true +--spire-trust-domain= # 'localtest.me' in Kind by default +--spire-trust-bundle-configmap=spire-bundle +--spire-trust-bundle-configmap-namespace= # 'spire-system' in Kind, 'zero-trust-workload-identity-manager' in OpenShift by default +``` + +If SPIRE was installed alongside Kagenti with the script above or similar, you can run the following helm command to apply the required flags: -### 1. Build Images +```bash +KAGENTI_REPO= +helm upgrade kagenti "$KAGENTI_REPO/charts/kagenti/" \ + -n kagenti-system \ + --reuse-values \ + -f "$KAGENTI_REPO/charts/kagenti/.secrets.yaml" \ + --set kagenti-operator-chart.signatureVerification.enabled=true \ + --set kagenti-operator-chart.signatureVerification.enforceNetworkPolicies=true \ + --set kagenti-operator-chart.signatureVerification.spireTrustDomain= \ + --set kagenti-operator-chart.signatureVerification.spireTrustBundle.configMapName=spire-bundle \ + --set kagenti-operator-chart.signatureVerification.spireTrustBundle.configMapNamespace= +``` -Build the agentcard-signer init-container image and load it into Kind: +You can check the name of the spire domain with the following command: ```bash -cd kagenti-operator/ +kubectl get configmap spire-server -n zero-trust-workload-identity-manager -o jsonpath='{.data.server\.conf}{"\n"}' | grep trust_domain +``` -# Build the signer image -make build-signer +## OpenShift-Specific Prerequisites -# Load into Kind (default cluster name is "kagenti") -make load-signer-image +OpenShift enforces additional security rules (using SELinux, SCC, NetworkPolicy, etc.) which require additional setup to enable SPIRE signing. Run the following commands in addition to the prerequisites above: -# Or specify a different cluster name -make load-signer-image KIND_CLUSTER_NAME= +```bash +oc adm policy add-scc-to-user privileged -z spire-spiffe-csi-driver -n zero-trust-workload-identity-manager +oc label ns kagenti-system control-plane=kagenti-operator ``` -### 2. Configure the Operator +The commands above allow ingress to the workload pods from the Kagenti operator and provide `privileged` SCC to SPIRE CSI driver needed to mount SPIRE agent socket and obtain SVID successfully. -The operator must be started with these flags for signature verification: +In addition, Zero Trust Workload Identity Manager in OpenShift loads trust bundle data into `data.bundle.crt` instead of `data.bundle.spiffe` in the bundle ConfigMap, leading to the operator not reading the trust bundle data successfully. You can override the default bundle path in the operator with the following helm command (assuming Kagenti was installed using a script above or similar): -``` ---require-a2a-signature=true ---spire-trust-domain= ---spire-trust-bundle-configmap=spire-bundle ---spire-trust-bundle-configmap-namespace=spire-system ---enforce-network-policies=true +```bash +KAGENTI_REPO= +helm upgrade kagenti "$KAGENTI_REPO/charts/kagenti/" \ + -n kagenti-system \ + --reuse-values \ + -f "$KAGENTI_REPO/charts/kagenti/.secrets.yaml" \ + --set kagenti-operator-chart.signatureVerification.spireTrustBundle.configMapKey=bundle.crt ``` -If using the Helm chart, set these in your values override. +## Setup -### 3. Deploy the Demo +### 1. Deploy the Demo ```bash +cd /kagenti-operator kubectl apply -f demos/agentcard-spire-signing/k8s/namespace.yaml kubectl apply -f demos/agentcard-spire-signing/k8s/clusterspiffeid.yaml kubectl apply -f demos/agentcard-spire-signing/k8s/agent-deployment.yaml -kubectl apply -f demos/agentcard-spire-signing/k8s/agentcard.yaml ``` -### 4. Wait for Pods +If on OpenShift, in addition to above run: + +```bash +oc adm policy add-scc-to-user privileged -z weather-agent-sa -n agents # Overrides SELinux rule preventing access to the SPIRE agent socket +oc label ns agents pod-security.kubernetes.io/enforce=privileged --overwrite # Allows pods in `agents` namespace to mount CSI driver volumes +``` + +### 2. Wait for Pods ```bash kubectl wait --for=condition=available --timeout=120s deployment/weather-agent -n agents ``` -## Test the Flow +### 3. Test the Flow Run the demo script to see signing and verification in action: @@ -123,7 +147,7 @@ Expected output: === 7. AgentCard Summary === NAME PROTOCOL KIND TARGET AGENT VERIFIED BOUND SYNCED ... -weather-agent-card a2a Deployment weather-agent Weather Agent true true True ... +weather-agent-deployment-card a2a Deployment weather-agent Weather Agent true true True ... ``` ## How It Works @@ -148,8 +172,35 @@ Use the teardown script to delete all demo resources: Or manually: ```bash -kubectl delete -f demos/agentcard-spire-signing/k8s/agentcard.yaml kubectl delete -f demos/agentcard-spire-signing/k8s/agent-deployment.yaml kubectl delete -f demos/agentcard-spire-signing/k8s/clusterspiffeid.yaml kubectl delete -f demos/agentcard-spire-signing/k8s/namespace.yaml ``` + +## Troubleshooting + +### Pull rate limit error for `docker.io/python:3.11-slim` + +If you run into image pull rate limit on OpenShift, you can patch the deployment to use a Red Hat UBI Python image: + +```bash +oc patch deployment weather-agent -n agents --type=json -p='[{"op":"replace","path":"/spec/template/spec/containers/0/image","value":"registry.redhat.io/ubi9/python-311:latest"}]' +``` + +### Error pulling `agentcard-signer` image for `ghcr.io` + +You can build your own image and upload it to Kind/OpenShift internal registry with the following commands: + +```bash +cd kagenti-operator/ + +# Kind +make build-signer # Build the signer image +make load-signer-image KIND_CLUSTER_NAME=kagenti # Load the signer image into the default "kagenti" cluster + +# OpenShift +oc new-build -n agents --name agentcard-signer --binary --strategy docker --to=agentcard-signer # Create a binary BuildConfig that outputs the signer image +oc patch bc/agentcard-signer -n agents --type=json -p='[{"op":"add","path":"/spec/strategy/dockerStrategy/dockerfilePath","value":"cmd/agentcard-signer/Dockerfile"}]' # Point the BuildConfig to the signer Dockerfile path in this repo +oc start-build agentcard-signer -n agents --from-dir=. # Upload the current directory as build context and start the image build +oc patch deployment weather-agent -n agents --type=json -p='[{"op":"replace","path":"/spec/template/spec/initContainers/0/image","value":"image-registry.openshift-image-registry.svc:5000/agents/agentcard-signer:latest"}]' # Use the newly built signer image from the OpenShift internal registry +``` diff --git a/kagenti-operator/demos/agentcard-spire-signing/k8s/agentcard.yaml b/kagenti-operator/demos/agentcard-spire-signing/k8s/agentcard.yaml deleted file mode 100644 index 1f619a06..00000000 --- a/kagenti-operator/demos/agentcard-spire-signing/k8s/agentcard.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: agent.kagenti.dev/v1alpha1 -kind: AgentCard -metadata: - name: weather-agent-card - namespace: agents -spec: - syncPeriod: "30s" - targetRef: - apiVersion: apps/v1 - kind: Deployment - name: weather-agent - identityBinding: - strict: true diff --git a/kagenti-operator/demos/agentcard-spire-signing/run-demo-commands.sh b/kagenti-operator/demos/agentcard-spire-signing/run-demo-commands.sh index cd9c8f9c..12779527 100755 --- a/kagenti-operator/demos/agentcard-spire-signing/run-demo-commands.sh +++ b/kagenti-operator/demos/agentcard-spire-signing/run-demo-commands.sh @@ -27,7 +27,7 @@ print(f' Signatures: {len(d.get(\"signatures\", []))}') echo "" echo "=== 3. JWS Protected Header ===" -kubectl get agentcard weather-agent-card -n "$NAMESPACE" \ +kubectl get agentcard weather-agent-deployment-card -n "$NAMESPACE" \ -o jsonpath='{.status.card.signatures[0].protected}' | python3 -c " import sys, base64, json b64 = sys.stdin.read().strip() @@ -40,7 +40,7 @@ print(f' x5c certs: {len(header.get(\"x5c\", []))}') echo "" echo "=== 4. Operator Verification Status ===" -kubectl get agentcard weather-agent-card -n "$NAMESPACE" \ +kubectl get agentcard weather-agent-deployment-card -n "$NAMESPACE" \ -o jsonpath='{.status.conditions}' | python3 -c " import sys, json for c in json.loads(sys.stdin.read()): @@ -54,7 +54,7 @@ for c in json.loads(sys.stdin.read()): echo "" echo "=== 5. Identity Binding ===" -kubectl get agentcard weather-agent-card -n "$NAMESPACE" \ +kubectl get agentcard weather-agent-deployment-card -n "$NAMESPACE" \ -o jsonpath='{.status}' | python3 -c " import sys, json s = json.loads(sys.stdin.read()) diff --git a/kagenti-operator/demos/agentcard-spire-signing/teardown-demo.sh b/kagenti-operator/demos/agentcard-spire-signing/teardown-demo.sh index d7173f5c..a0771c0a 100755 --- a/kagenti-operator/demos/agentcard-spire-signing/teardown-demo.sh +++ b/kagenti-operator/demos/agentcard-spire-signing/teardown-demo.sh @@ -15,7 +15,6 @@ echo "=== SPIRE Signing Demo Teardown ===" echo "" echo "Deleting Kubernetes resources..." -kubectl delete -f "${K8S_DIR}/agentcard.yaml" --ignore-not-found=true 2>/dev/null || true kubectl delete -f "${K8S_DIR}/agent-deployment.yaml" --ignore-not-found=true 2>/dev/null || true kubectl delete -f "${K8S_DIR}/clusterspiffeid.yaml" --ignore-not-found=true 2>/dev/null || true echo "Kubernetes resources deleted."