Feat: add SPIRE operand controller for ZTWIM CR and SPIRE CRs lifecycle#465
Conversation
199173c to
85a802b
Compare
…oller Rename ztwimGVK → ZTWIMGVK so the SPIRE operand controller can reference the GVK from the discovery package (DRY — single source of truth). Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Ian Miller <milleryan2003@gmail.com>
Implement SpireOperandReconciler + SpireBootstrapRunnable that create and reconcile 5 SPIRE operand CRs (ZTWIM, SpiffeCSIDriver, SpireServer, SpireAgent, SpireOIDCDiscoveryProvider) when ZTWIM CRDs are present. - CRD-gated activation (no feature flag) via SpireOperandCRDExists() - Bootstrap runnable creates ZTWIM at startup, triggers controller watch - Drift correction via mergeNestedMap into existing spec (preserves ZTWIM operator defaults, avoids reconcile loops) - Preserves immutable trustDomain from existing ZTWIM CR - RBAC markers for all 5 operator.openshift.io resources - Event recording on create/update Replaces fragile Helm post-install hooks (RHAIENG-5468). Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Ian Miller <milleryan2003@gmail.com>
37 Ginkgo tests covering: - CRD guard (no-op when ZTWIM absent) - ZTWIM and 4 child CR creation with spec validation - Drift correction (ZTWIM trustDomain, child spec) - trustDomain immutability preservation - Idempotency (double reconcile) - Bootstrap runnable (create, skip, leader election) - Spec builder assertions (no trustDomain on children) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Ian Miller <milleryan2003@gmail.com>
8 Ginkgo e2e tests with mock ZTWIM CRDs for Kind: - All 5 CRs created with managed-by label - ZTWIM, SpiffeCSIDriver spec correctness - Children don't contain trustDomain - Drift correction (child delete, ZTWIM spec patch) - Controller activation log verification Mock CRDs use x-kubernetes-preserve-unknown-fields for Kind-based testing without real ZTWIM operator. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Ian Miller <milleryan2003@gmail.com>
Register SpireBootstrapRunnable and SpireOperandReconciler when SpireOperandCRDExists() detects ZTWIM CRDs. Gracefully skip when trust domain is not yet discoverable. No feature flag — mirrors TektonConfigCRDExists/CertManagerCRDExists pattern. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Ian Miller <milleryan2003@gmail.com>
Add create/get/list/update/watch for operator.openshift.io resources: spiffecsidrivers, spireservers, spireagents, spireoidcdiscoveryproviders, zerotrustworkloadidentitymanagers. Generated by make manifests from kubebuilder RBAC markers. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Ian Miller <milleryan2003@gmail.com>
- architecture.md: controller section, reconciliation flow, managed CRs table, watches table, drift correction, RBAC permissions, architecture diagram with SPIRE Infrastructure subgraph - operator.md: controller listing Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Ian Miller <milleryan2003@gmail.com>
- gofmt: fix map literal alignment in test - staticcheck: use fmt.Fprintf instead of WriteString(fmt.Sprintf) - unparam: remove unused OperationResult return from ensureUnstructuredCR Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Ian Miller <milleryan2003@gmail.com>
In Kind CI, no ZTWIM CR or spire-bundle ConfigMap exists for trust domain auto-discovery. Set KAGENTI_SPIRE_TRUST_DOMAIN env var on the operator deployment so the SPIRE operand controller activates. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Ian Miller <milleryan2003@gmail.com>
make manifests on this branch dropped authorizationpolicies RBAC rules because the AuthorizationPolicy type exists on main but not on this branch. Restore them to avoid breaking the other PR on merge. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Ian Miller <milleryan2003@gmail.com>
MLflow operand controller documentation is unrelated to this PR. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Ian Miller <milleryan2003@gmail.com>
Add create/get/list/update/watch for operator.openshift.io SPIRE resources (spiffecsidrivers, spireservers, spireagents, spireoidcdiscoveryproviders, zerotrustworkloadidentitymanagers) to the Helm chart's ClusterRole template. Mirrors the kubebuilder-generated config/rbac/role.yaml. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Ian Miller <milleryan2003@gmail.com>
- Fix SPIRE e2e test: use kagenti-operator-controller-manager (kustomize namePrefix) instead of kagenti-controller-manager - Reduce SpireOperandCRDExists retry delays from attempt*5s to attempt*1s to avoid blocking manager start for 15s, which caused webhook connection refused in AgentRuntime e2e tests Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Ian Miller <milleryan2003@gmail.com>
85a802b to
1d7e50d
Compare
rh-dnagornuks
left a comment
There was a problem hiding this comment.
Solid changes. Seems to work based on my tests using the image built from this branch. One blocker (in an inline comment) and a nit.
Nit (for the overall PR): commit history is a bit messy and rebase squash could have been used in fix up commits such as fix(e2e): correct deployment name and reduce CRD retry delays
pdettori
left a comment
There was a problem hiding this comment.
Well-structured controller implementation with good test coverage (37 unit + 8 e2e), proper CRD gating, and thoughtful trustDomain immutability handling. The mergeNestedMap approach for drift correction without reconcile loops is solid design. CI fully green (all 16 checks pass).
A couple of non-blocking suggestions for robustness and configurability below.
Areas reviewed: Go controller, Go unit tests, E2E tests, Helm RBAC, Discovery, Docs
Commits: 13 commits, all signed-off with DCO
CI status: All 16 checks passing
- Surface non-NotFound errors in bootstrap Start() instead of falling through to Create() (pdettori suggestion) - Make clusterName configurable via KAGENTI_SPIRE_CLUSTER_NAME env var and Helm value signatureVerification.spireClusterName, defaulting to "agent-platform" (pdettori suggestion) - Handle clusterName immutability same as trustDomain — read existing value from ZTWIM CR to avoid rejected updates - Drop authorizationpolicies RBAC rules from role.yaml — no kubebuilder markers, not on main (pdettori + rh-dnagornuks) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Ian Miller <milleryan2003@gmail.com>
rh-dnagornuks
left a comment
There was a problem hiding this comment.
Feedback addressed. LGTM
clusterName is immutable on the ZTWIM CR — the controller now preserves existing values. Change the drift test to patch bundleConfigMap (mutable) instead. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Ian Miller <milleryan2003@gmail.com>
|
Could the drift test for ZTWIM delete the controller itself rather than a field within it? The main 3 fields ( |
Summary
SpireOperandReconciler+SpireBootstrapRunnablethat create and reconcile 5 SPIRE operand CRs (operator.openshift.io/v1alpha1) when ZTWIM CRDs are presentTektonConfigCRDExists/CertManagerCRDExistspatternChanges
internal/controller/spire_operand_controller.gomergeNestedMapinternal/controller/spire_operand_controller_test.gotest/e2e/spire_operand_test.gointernal/discovery/discovery.goztwimGVK→ZTWIMGVKcmd/main.goconfig/rbac/role.yamldocs/architecture.mddocs/operator.mdManaged CRs
All
operator.openshift.io/v1alpha1, cluster-scoped, name"cluster":Key Design Decisions
disableMigration), avoids reconcile update loopsunstructured.Unstructuredto avoid importing OCP ZTWIM operator Go types as a dependencyVerification Report: RHAIENG-5468 — SPIRE Operand Controller
Date: 2026-06-23
Feature: SPIRE/ZTWIM Operand Controller
Jira: RHAIENG-5468
Branch:
create-controller-for-spire-and-ztwimEnvironment
image-registry.openshift-image-registry.svc:5000/kagenti-system/kagenti-operator:spire-operandapps.rosa.ian-cluster.g532.p3.openshiftapps.comUnit Test Results
Command:
Output:
Tests cover: CRD guard, ZTWIM creation, 4 child CR creation, spec validation,
drift correction, idempotency, bootstrap runnable, trustDomain immutability,
spec builder assertions.
Acceptance Criteria
AC1: Controller is no-op when CRDs not installed — PASS
Method: Unit tests + operator startup logs.
Command (unit test):
Output:
Command (operator startup — CRDs present):
Output:
{"level":"info","ts":"2026-06-23T11:31:21Z","logger":"setup","msg":"Auto-discovered SPIRE trust domain","trustDomain":"localtest.me"} {"level":"info","ts":"2026-06-23T11:31:21Z","logger":"controller.SpireOperand","msg":"ZTWIM CRD detected: will manage SPIRE operand CRs"} {"level":"info","ts":"2026-06-23T11:31:21Z","logger":"setup","msg":"SPIRE operand controller enabled","trustDomain":"localtest.me"}Controller detects CRDs and activates. On clusters without CRDs, it logs
"ZTWIM CRD not registered, SPIRE operand controller will not start" and
returns nil (unit tested).
AC2: Operator creates all 5 SPIRE CRs when CRDs present — PASS
From-scratch creation with auto-discovery (production behavior)
ZTWIM parent CR exists on cluster (created by admin or Helm). No env var overrides.
Operator auto-discovers trustDomain from ZTWIM CR, creates 4 children.
Command (delete children, restart operator):
Command (verify auto-discovery + creation):
Output:
{"level":"info","ts":"2026-06-23T11:31:21Z","logger":"setup","msg":"Auto-discovered SPIRE trust domain","trustDomain":"localtest.me"} {"level":"info","ts":"2026-06-23T11:31:21Z","logger":"setup","msg":"SPIRE operand controller enabled","trustDomain":"localtest.me"} {"level":"info","ts":"2026-06-23T11:31:36Z","logger":"spire-bootstrap","msg":"SPIRE bootstrap: ensuring ZTWIM CR exists"} {"level":"info","ts":"2026-06-23T11:31:36Z","logger":"spire-bootstrap","msg":"SPIRE bootstrap: ZTWIM CR already exists, skipping creation"} {"level":"info","ts":"2026-06-23T11:31:36Z","msg":"Created SPIRE operand CR","controller":"spire-operand","controllerGroup":"operator.openshift.io","controllerKind":"ZeroTrustWorkloadIdentityManager","ZeroTrustWorkloadIdentityManager":{"name":"cluster"},"namespace":"","name":"cluster","reconcileID":"a42ed70a-de26-4bf0-8a2b-568539ded430","kind":"SpiffeCSIDriver","name":"cluster"} {"level":"info","ts":"2026-06-23T11:31:36Z","msg":"Created SPIRE operand CR","controller":"spire-operand","controllerGroup":"operator.openshift.io","controllerKind":"ZeroTrustWorkloadIdentityManager","ZeroTrustWorkloadIdentityManager":{"name":"cluster"},"namespace":"","name":"cluster","reconcileID":"a42ed70a-de26-4bf0-8a2b-568539ded430","kind":"SpireServer","name":"cluster"} {"level":"info","ts":"2026-06-23T11:31:36Z","msg":"Created SPIRE operand CR","controller":"spire-operand","controllerGroup":"operator.openshift.io","controllerKind":"ZeroTrustWorkloadIdentityManager","ZeroTrustWorkloadIdentityManager":{"name":"cluster"},"namespace":"","name":"cluster","reconcileID":"a42ed70a-de26-4bf0-8a2b-568539ded430","kind":"SpireAgent","name":"cluster"} {"level":"info","ts":"2026-06-23T11:31:36Z","msg":"Created SPIRE operand CR","controller":"spire-operand","controllerGroup":"operator.openshift.io","controllerKind":"ZeroTrustWorkloadIdentityManager","ZeroTrustWorkloadIdentityManager":{"name":"cluster"},"namespace":"","name":"cluster","reconcileID":"a42ed70a-de26-4bf0-8a2b-568539ded430","kind":"SpireOIDCDiscoveryProvider","name":"cluster"} {"level":"info","ts":"2026-06-23T11:31:36Z","msg":"SPIRE operand reconciliation complete","controller":"spire-operand","controllerGroup":"operator.openshift.io","controllerKind":"ZeroTrustWorkloadIdentityManager","ZeroTrustWorkloadIdentityManager":{"name":"cluster"},"namespace":"","name":"cluster","reconcileID":"a42ed70a-de26-4bf0-8a2b-568539ded430"} {"level":"info","ts":"2026-06-23T11:31:37Z","msg":"SPIRE operand reconciliation complete","controller":"spire-operand","controllerGroup":"operator.openshift.io","controllerKind":"ZeroTrustWorkloadIdentityManager","ZeroTrustWorkloadIdentityManager":{"name":"cluster"},"namespace":"","name":"cluster","reconcileID":"3b382ff5-425c-4694-95ef-e48a62ea00db"}No env vars, no manual steps. Trust domain auto-discovered from existing ZTWIM CR.
Verify all 5 CRs and labels
Command:
Output:
Command (verify ZTWIM spec):
Output:
{ "bundleConfigMap": "spire-bundle", "clusterName": "agent-platform", "trustDomain": "localtest.me" }Command (verify SpiffeCSIDriver spec):
Output:
{ "agentSocketPath": "/run/spire/agent-sockets", "pluginName": "csi.spiffe.io" }Command (verify SpireAgent has no trustDomain — inherited from parent):
oc get spireagents.operator.openshift.io cluster -o jsonpath='{.spec.trustDomain}'Output:
AC3: ZTWIM parent created before children — PASS
Command:
Output:
{"level":"info","ts":"2026-06-23T11:31:36Z","logger":"spire-bootstrap","msg":"SPIRE bootstrap: ensuring ZTWIM CR exists"} {"level":"info","ts":"2026-06-23T11:31:36Z","logger":"spire-bootstrap","msg":"SPIRE bootstrap: ZTWIM CR already exists, skipping creation"} {"level":"info","ts":"2026-06-23T11:31:36Z","msg":"Created SPIRE operand CR","controller":"spire-operand","controllerGroup":"operator.openshift.io","controllerKind":"ZeroTrustWorkloadIdentityManager","ZeroTrustWorkloadIdentityManager":{"name":"cluster"},"namespace":"","name":"cluster","reconcileID":"a42ed70a-de26-4bf0-8a2b-568539ded430","kind":"SpiffeCSIDriver","name":"cluster"} {"level":"info","ts":"2026-06-23T11:31:36Z","msg":"Created SPIRE operand CR","controller":"spire-operand","controllerGroup":"operator.openshift.io","controllerKind":"ZeroTrustWorkloadIdentityManager","ZeroTrustWorkloadIdentityManager":{"name":"cluster"},"namespace":"","name":"cluster","reconcileID":"a42ed70a-de26-4bf0-8a2b-568539ded430","kind":"SpireServer","name":"cluster"} {"level":"info","ts":"2026-06-23T11:31:36Z","msg":"Created SPIRE operand CR","controller":"spire-operand","controllerGroup":"operator.openshift.io","controllerKind":"ZeroTrustWorkloadIdentityManager","ZeroTrustWorkloadIdentityManager":{"name":"cluster"},"namespace":"","name":"cluster","reconcileID":"a42ed70a-de26-4bf0-8a2b-568539ded430","kind":"SpireAgent","name":"cluster"} {"level":"info","ts":"2026-06-23T11:31:36Z","msg":"Created SPIRE operand CR","controller":"spire-operand","controllerGroup":"operator.openshift.io","controllerKind":"ZeroTrustWorkloadIdentityManager","ZeroTrustWorkloadIdentityManager":{"name":"cluster"},"namespace":"","name":"cluster","reconcileID":"a42ed70a-de26-4bf0-8a2b-568539ded430","kind":"SpireOIDCDiscoveryProvider","name":"cluster"} {"level":"info","ts":"2026-06-23T11:31:36Z","msg":"SPIRE operand reconciliation complete","controller":"spire-operand","controllerGroup":"operator.openshift.io","controllerKind":"ZeroTrustWorkloadIdentityManager","ZeroTrustWorkloadIdentityManager":{"name":"cluster"},"namespace":"","name":"cluster","reconcileID":"a42ed70a-de26-4bf0-8a2b-568539ded430"} {"level":"info","ts":"2026-06-23T11:31:37Z","msg":"SPIRE operand reconciliation complete","controller":"spire-operand","controllerGroup":"operator.openshift.io","controllerKind":"ZeroTrustWorkloadIdentityManager","ZeroTrustWorkloadIdentityManager":{"name":"cluster"},"namespace":"","name":"cluster","reconcileID":"3b382ff5-425c-4694-95ef-e48a62ea00db"}Bootstrap ensures ZTWIM first, then children in order: SpiffeCSIDriver → SpireServer → SpireAgent → SpireOIDCDiscoveryProvider.
AC4: Drift reconciliation — PASS
Command:
Output:
SpireAgent recreated within 10 seconds with correct
managed-by=kagenti-operatorlabel.AC5: Zero trust — SPIFFE volumes mounted — PASS
Command:
Output:
Command (verify CSI driver is the source):
oc -n test-zero-trust get pod -l app.kubernetes.io/name=weather-service \ -o jsonpath='{.items[0].spec.volumes[?(@.name=="spire-agent-socket")].csi.driver}'Output:
Command (verify SPIRE infrastructure pods running):
Output:
All SPIRE components reconciled by ZTWIM operator from CRs our controller created.
3 agents + 3 CSI drivers = 3 worker nodes (DaemonSets).
AC6: AgentCard signature verification — PASS
Setup: Built agentcard-signer image (
cmd/agentcard-signer/Dockerfile) forlinux/amd64, pushed to OCP internal registry. Deployed signed-agent stack with
SPIRE CSI volume, ServiceAccount, Role/RoleBinding, unsigned card ConfigMap.
Command (verify signer fetched SVID and signed card):
Output:
{"level":"info","msg":"starting agentcard signer","signed_path":"/app/.well-known/agent-card.json","socket":"unix:///run/spire/agent-sockets/spire-agent.sock","timeout":"30s","ts":"2026-06-23T10:48:18Z","unsigned_path":"/etc/agentcard/agent.json"} {"level":"info","msg":"fetched SVID","spiffe_id":"spiffe://localtest.me/ns/test-zero-trust/sa/signed-agent-sa","ts":"2026-06-23T10:48:19Z"} {"level":"info","msg":"signed card written successfully","output_path":"/app/.well-known/agent-card.json","spiffe_id":"spiffe://localtest.me/ns/test-zero-trust/sa/signed-agent-sa","ts":"2026-06-23T10:48:19Z"} {"configMap":"signed-agent-card-signed","level":"info","msg":"signed card written to ConfigMap","namespace":"test-zero-trust","ts":"2026-06-23T10:48:19Z"}Command (verify AgentCard auto-created and synced):
Output:
Command (decode JWS protected header — verify x5c chain and algorithm):
Output:
{ "alg": "ES256", "typ": "JOSE", "kid": "2d644473ae350c16", "x5c": [ "MIIDCDCCAfCgAwIBAgIQGHtPqrZcdMerCKwt+W19NzANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzELMAkGA1UEChMCUkgxFTATBgNVBAMTDGxvY2FsdGVzdC5tZTEwMC4GA1UEBRMnMTQyNDQ2MTUxMDE5NjcxODQ1MzE3Njg5Mzg0MTQ0MDU3MDM1Mjg0MB4XDTI2MDYyMzEwNDc1NloXDTI2MDYyMzExNDgwNlowHTELMAkGA1UEBhMCVVMxDjAMBgNVBAoTBVNQSVJFMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiqfrEEpkPcfUZawqEQ80SCg00aRTaEFpExS1L8y1+JN08CVIfoqHy2TPRVcaAMxL4SogBzpv3rprJFqtbIwoh6OByDCBxTAOBgNVHQ8BAf8EBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFLDP2T3i607VTY/DAjvBPCKFOSCtMB8GA1UdIwQYMBaAFFas9DHmieY4aBBjITaXheKgV2WjMEYGA1UdEQQ/MD2GO3NwaWZmZTovL2xvY2FsdGVzdC5tZS9ucy90ZXN0LXplcm8tdHJ1c3Qvc2Evc2lnbmVkLWFnZW50LXNhMA0GCSqGSIb3DQEBCwUAA4IBAQA45s3Ry4BpP4sTd3StWiv4pM/Y97Ge41caoJHHAZwZkm0et3oRlFWRwGh4HXaQp73+x1KsPqc3Sg4RJ5PdXuMKC20HM7V6AZbUlvpsaHaj2Imm4H6aIdlziaJqHBrUlwHJvmYG7XEo6i9nw0BX/5lUBv9vnoTD9EewcBGBrTA50NgOyMH843z9tU4d4RsZvY7LUpTf/B9ZG8OfHZ7MRJsIYznBk1NRFhHn3u+R7HU/woJ5VV8XfFDFOAUhWJIarpax+XYCxxaRsYu/d9efxdAFzl5hqmnMi6/olvX+lXeoUUwQrFbT8rURd5Sz25dGc2uEqRsVp0fukMozqmU7rCqV" ] }Command (verify signature count):
Output:
Full chain verified: SPIRE operand CRs (our controller) → ZTWIM operator →
SPIRE running → CSI driver mounts socket → agentcard-signer fetches SVID
(
spiffe://localtest.me/ns/test-zero-trust/sa/signed-agent-sa) → signs cardwith ES256/x5c → operator fetches signed card → Synced=True.
AC7: Full stack — weather agent tells weather in Dublin — PASS
Setup: weather_service (A2A agent) + weather_tool (MCP server) deployed in
test-zero-trustnamespace. OpenAI gpt-4o-mini as LLM backend. LLM credentialsvia
llm-credentialsSecret.Command:
Output:
{"id":"8","jsonrpc":"2.0","result":{"artifacts":[{"artifactId":"abbd39b2-c374-49c4-9578-30fc3147c355","parts":[{"kind":"text","text":"The current weather in Dublin is as follows:\n- **Temperature:** 77.1°F\n- **Windspeed:** 8.3 mph\n- **Wind Direction:** 99° (east)\n- **Daytime:** Yes\n\nIf you need more specific details or forecasts, feel free to ask!"}]}],"contextId":"96f5f508-2030-4471-a006-e388d95f067f","history":[{"contextId":"96f5f508-2030-4471-a006-e388d95f067f","kind":"message","messageId":"dublin-7","parts":[{"kind":"text","text":"What is the weather in Dublin?"}],"role":"user","taskId":"a1a9e40c-b51b-43cd-9dc9-c368123d3af9"},{"contextId":"96f5f508-2030-4471-a006-e388d95f067f","kind":"message","messageId":"427d1599-1c0d-47be-b700-40977c816b4c","parts":[{"kind":"text","text":"🚶♂assistant: {'messages': [HumanMessage(content='What is the weather in Dublin?', additional_kwargs={}, response_metadata={}, id='6190bded-83a3-4f48-96f4-e0447e70978e'), AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'comp..."}],"role":"agent","taskId":"a1a9e40c-b51b-43cd-9dc9-c368123d3af9"},{"contextId":"96f5f508-2030-4471-a006-e388d95f067f","kind":"message","messageId":"86a288ec-8d14-41c0-bcc6-3277bff0d332","parts":[{"kind":"text","text":"🚶♂tools: {'messages': [ToolMessage(content=[{'type': 'text', 'text': '{\"time\": \"2026-06-23T11:45\", \"interval\": 900, \"temperature\": 77.1, \"windspeed\": 8.3, \"winddirection\": 99, \"is_day\": 1, \"weathercode\": 3}', 'id': 'lc_28e5fa64-1490-4e44-9c92-70cbce9beb31'}], name=..."}],"role":"agent","taskId":"a1a9e40c-b51b-43cd-9dc9-c368123d3af9"}],"id":"a1a9e40c-b51b-43cd-9dc9-c368123d3af9","kind":"task","status":{"state":"completed","timestamp":"2026-06-23T11:48:24.314130+00:00"}}}Full A2A trace visible: User → AIMessage (tool call decision) → ToolMessage
(MCP
get_weatherreturned{"temperature": 77.1, "windspeed": 8.3}) →AIMessage (formatted response). Task state: completed.
mTLS Identity Layer Verification (weather-service A2A agent)
SPIFFE CSI Volume Injection — VERIFIED
AuthBridge mutating webhook injects SPIRE volumes into weather-service pod.
Command (list all pod volumes):
Output:
Command (verify CSI driver source):
Output:
{ "csi": { "driver": "csi.spiffe.io", "readOnly": true }, "name": "spire-agent-socket" }Command (verify CSI driver name):
oc -n test-zero-trust get pod -l app.kubernetes.io/name=weather-service \ -o jsonpath='{.items[0].spec.volumes[?(@.name=="spire-agent-socket")].csi.driver}'Output:
CSI driver
csi.spiffe.iomatches our SpiffeCSIDriver CR (pluginName: csi.spiffe.io).AuthBridge Sidecar Containers — VERIFIED
Command:
oc -n test-zero-trust get pod -l app.kubernetes.io/name=weather-service \ -o jsonpath='{range .items[0].spec.containers[*]}{.name}{"\n"}{end}'Output:
Two containers:
agent(application) andauthbridge-proxy(mTLS sidecar injectedby AuthBridge mutating webhook).
AuthBridge Volume Mounts — VERIFIED
Command:
Output:
[ { "mountPath": "/shared", "name": "shared-data" }, { "mountPath": "/etc/authbridge", "name": "authbridge-runtime-config", "readOnly": true }, { "mountPath": "/etc/authproxy", "name": "authproxy-routes", "readOnly": true }, { "mountPath": "/opt", "name": "svid-output" }, { "mountPath": "/etc/spiffe-helper", "name": "spiffe-helper-config", "readOnly": true }, { "mountPath": "/spiffe-workload-api", "name": "spire-agent-socket", "readOnly": true }, { "mountPath": "/shared/client-secret.txt", "name": "kagenti-keycloak-client-credentials-c2b91e18c24b321b", "readOnly": true, "subPath": "client-secret.txt" }, { "mountPath": "/shared/client-id.txt", "name": "kagenti-keycloak-client-credentials-c2b91e18c24b321b", "readOnly": true, "subPath": "client-id.txt" }, { "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", "name": "kube-api-access-6ltdg", "readOnly": true } ]Key mTLS mounts on authbridge-proxy:
/spiffe-workload-api— SPIRE Workload API socket (CSIcsi.spiffe.io)/etc/spiffe-helper— spiffe-helper rotation config/opt— SVID output (svid.pem,svid_key.pem,svid_bundle.pem)/shared/client-secret.txt,/shared/client-id.txt— Keycloak credentials for token exchangespiffe-helper Configuration — VERIFIED
Command:
oc -n test-zero-trust get configmap spiffe-helper-config -o jsonpath='{.data.helper\.conf}'Output:
spiffe-helper connects to SPIRE Workload API socket and rotates X.509 SVIDs
(
svid.pem,svid_key.pem,svid_bundle.pem) into thesvid-outputemptyDir volume.AuthBridge mTLS Configuration — VERIFIED
Command:
oc -n test-zero-trust get configmap authbridge-config-weather-service -o jsonpath='{.data.config\.yaml}'Output:
AuthBridge runs as
proxy-sidecarwithmtls.mode: permissive. Inbound: JWTvalidation against Keycloak. Outbound: token exchange via
client_credentials.SPIRE Infrastructure Pods — VERIFIED
Command:
Output:
All SPIRE components reconciled by ZTWIM operator from CRs our controller created.
3 agents + 3 CSI drivers = 3 worker nodes (DaemonSets). OIDC discovery provider running.
AuthBridge Startup with mTLS — VERIFIED
Command (authbridge-proxy logs after fix):
Output:
Command (pod status — 2/2 Running):
Output:
SVID Verification — VERIFIED
spiffe-helper fetched X.509 SVID from SPIRE and wrote it to
/opt.Command (list SVID files):
oc -n test-zero-trust exec deploy/weather-service -c authbridge-proxy -- ls -la /opt/Output:
Command (decode SVID certificate):
Output:
SVID issued by SPIRE CA (
CN=localtest.me) with SPIFFE IDspiffe://localtest.me/ns/test-zero-trust/sa/weather-service.1-hour validity, auto-rotated by spiffe-helper. ECDSA P-256 key.
Full mTLS chain verified: Our SPIRE operand CRs → ZTWIM operator runs SPIRE →
CSI driver mounts Workload API socket → spiffe-helper fetches X.509 SVID →
writes
svid.pem/svid_key.pem/svid_bundle.pem→ AuthBridge uses them formTLS (permissive mode) + Keycloak token exchange.
Summary
All 7 acceptance criteria verified on ROSA OCP 4.19.18.
Test plan
go build ./test/e2e/)go vet ./...clean🤖 Generated with Claude Code