Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 81 additions & 30 deletions kagenti-operator/demos/agentcard-spire-signing/demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<your-trust-domain> # 'localtest.me' in Kind by default
--spire-trust-bundle-configmap=spire-bundle
--spire-trust-bundle-configmap-namespace=<spire-bundle-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=<path-to-your-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=<your-trust-domain> \
--set kagenti-operator-chart.signatureVerification.spireTrustBundle.configMapName=spire-bundle \
--set kagenti-operator-chart.signatureVerification.spireTrustBundle.configMapNamespace=<spire-bundle-namespace>
```

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=<your-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=<your-trust-domain>
--spire-trust-bundle-configmap=spire-bundle
--spire-trust-bundle-configmap-namespace=spire-system
--enforce-network-policies=true
```bash
KAGENTI_REPO=<path-to-your-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 <path-to-your-kagenti-operator-repo>/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:

Expand Down Expand Up @@ -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
Expand All @@ -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
```

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()):
Expand All @@ -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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
Loading