Skip to content

Feature: absorb spiffe-helper config into global kagenti-platform-config#437

Merged
pdettori merged 8 commits into
rossoctl:mainfrom
r3v5:absorb-spiffe-helper-config-into-global-kagenti-platform-config
Jun 24, 2026
Merged

Feature: absorb spiffe-helper config into global kagenti-platform-config#437
pdettori merged 8 commits into
rossoctl:mainfrom
r3v5:absorb-spiffe-helper-config-into-global-kagenti-platform-config

Conversation

@r3v5

@r3v5 r3v5 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add HelperConfig field to SpiffeConfig so PlatformConfig carries spiffe-helper helper.conf content
  • Controller derives per-namespace spiffe-helper-config ConfigMaps from PlatformConfig (always-overwrite, single source of truth)
  • Remove spiffe-helper-config from template-copy list — no longer blindly copied from kagenti-system
  • Include helperConfig in config hash so changes trigger rolling updates
  • Webhook no longer reads spiffe-helper-config CM at admission time (dead field removed)
  • Add spiffe section to Helm values.yaml with configurable helperConfig
  • Delete static template files and OpenShift kustomize patch

Test plan

  • Unit tests: 520 pass (all non-e2e), including new tests for ensureSpiffeHelperConfigMap (create, overwrite, idempotent, nil fallback), config hash change detection, schema round-trip, and template exclusion
  • Kind cluster integration: verified all Jira acceptance criteria
    • kagenti-platform-config CM contains spiffe.helperConfig
    • Creating AgentRuntime produces spiffe-helper-config CM in agent namespace
    • CM content matches platform config
    • Per-namespace CM no longer read by webhook
    • Helm values.yaml includes helperConfig with sensible defaults
    • Leftover CMs from upgrades ignored without error
    • Changing helperConfig triggers rolling update via config hash

Closes RHAIENG-4939

🤖 Generated with Claude Code

@r3v5
r3v5 requested a review from a team as a code owner June 16, 2026 09:55
@r3v5 r3v5 changed the title feat: absorb spiffe-helper config into global kagenti-platform-config Feature: absorb spiffe-helper config into global kagenti-platform-config Jun 16, 2026
@r3v5
r3v5 force-pushed the absorb-spiffe-helper-config-into-global-kagenti-platform-config branch from 3552380 to 5d568ba Compare June 16, 2026 14:37

@esnible esnible 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.

Review Summary

Clean, well-tested refactor that moves spiffe-helper-config from static templates to being derived from PlatformConfig (single source of truth, always-overwrite). The Helm install path is correct and verified end-to-end: defaults.spiffe.helperConfigkagenti-platform-config → loader → controller ensureSpiffeHelperConfigMap → namespace CM → volume_builder mount. Helm values.yaml and the Go DefaultSpiffeHelperConfig fallback are byte-identical, the dead SpiffeHelperConf field is cleanly removed across webhook/injector, and the new unit tests (create/overwrite/idempotent/nil-fallback, hash-change, YAML round-trip, DeepCopy, template-exclusion) are thorough and assertive.

One non-blocking concern about the OpenShift kustomize install path — see the inline comment. Posting as COMMENT (not REQUEST_CHANGES) since this may be a non-issue if OpenShift is now Helm-only.

Author: r3v5 (CONTRIBUTOR — returning external)
Areas reviewed: Go controller/webhook, Helm values, kustomize config, e2e fixtures, unit tests
Agent/IDE config (.claude/.vscode): none (supply-chain gate clean)
Commits: 4, all signed-off, conventional format
CI: 15/16 green, E2E pending

Assisted-By: Claude Code

@@ -12,4 +12,3 @@ resources:

patches:
- path: patches/authbridge-keycloak.yaml

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.

Possible regression on the OpenShift kustomize path. This PR also deletes patches/spiffe-helper-keycloak.yaml (and its reference here). That patch overrode the spiffe-helper jwt_audience from the public keycloak.localtest.me to the in-cluster keycloak-service.keycloak.svc:8080/realms/kagenti for OpenShift — mirroring the authbridge-keycloak.yaml patch that's kept right above this line.

The new single source of truth, kagenti-platform-config, is created only by the Helm chart (templates/manager/configmap-platform-defaults.yaml). The make deploy-openshift / kustomize build config/openshift path never creates it, so loader.go falls back to CompiledDefaults()DefaultSpiffeHelperConfig, which hardcodes the public keycloak.localtest.me:8080 audience. That won't resolve in-cluster on OpenShift.

If OpenShift is still installable via this kustomize overlay, the in-cluster audience override appears lost. Two ways to resolve:

  • add an OpenShift mechanism to set spiffe.helperConfig with the in-cluster audience (parallel to how authbridge-keycloak.yaml survives), or
  • if OpenShift is now Helm-only, remove/deprecate the config/openshift overlay so it isn't left in a broken state.

Could you confirm which install path OpenShift uses now?


// DefaultSpiffeHelperConfig is the default helper.conf content for spiffe-helper.
// Keep in sync with charts/kagenti-operator/values.yaml defaults.spiffe.helperConfig.
const DefaultSpiffeHelperConfig = `agent_address = "/spiffe-workload-api/spire-agent.sock"

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.

Nit: DefaultSpiffeHelperConfig is the compiled fallback used whenever kagenti-platform-config is absent, and it bakes in the public keycloak.localtest.me audience. That's fine as a dev default, but it's also the silent fallback for any deploy path that doesn't create the platform-config CM (see the kustomize/OpenShift comment). A one-line note here that prod/OpenShift must override this via PlatformConfig would make the failure mode less surprising.

@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.

Reviewed the Go controller/webhook changes, Helm values+template, kustomize, and e2e fixtures. Two things I verified and consider correct (no action needed):

  • Helm wiring: defaults.spiffe.helperConfig in values.yaml is rendered into kagenti-platform-config via templates/manager/configmap-platform-defaults.yaml (.Values.defaults | toYaml). ✓
  • Reconcile ordering: ensureSpiffeHelperConfigMap (step 4.5b) runs before ComputeConfigHash (step 5), so the hash reflects the freshly-written CM — no one-reconcile lag. ✓

One low-severity suggestion inline. (I'm intentionally not re-raising the OpenShift jwt_audience regression already noted in the existing review — that remains the more consequential open item.)

Assisted-By: Claude Code

return fmt.Errorf("failed to check spiffe-helper-config in %s: %w", namespace, err)
}

if existing.Data["helper.conf"] == cfg.Spiffe.HelperConfig {

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.

suggestion: The idempotent early-return skips label reconciliation. When existing.Data["helper.conf"] already matches the platform config, the function returns here — before the existing.Labels[LabelManagedBy] = LabelManagedByValue block just below.

This means a spiffe-helper-config CM left over from the old static-template/copy path (labeled app.kubernetes.io/managed-by: kustomize, or unlabeled) whose content happens to equal the new default gets adopted in content but never receives the kagenti.io/managed-by ownership marker this single-source-of-truth design relies on. The new "idempotent when content matches" test doesn't catch it because it pre-seeds the CM with the label.

Consider reconciling the label before the content-equality short-circuit (e.g. ensure/patch the label whenever it's missing, independent of the data comparison).

@r3v5

r3v5 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Addressing all three review comments:

@esnible — OpenShift kustomize path (kustomization.yaml:14)
OpenShift uses Helm for production installs. The kustomize overlay (config/openshift) is kept for dev/CI convenience (make deploy-openshift) but production OpenShift goes through Helm, which creates kagenti-platform-config with the correct jwt_audience from values. The old spiffe-helper-keycloak.yaml patch was only needed because the static template had a hardcoded audience. Now helperConfig flows through PlatformConfig — OpenShift Helm profiles set defaults.spiffe.helperConfig with the in-cluster Keycloak audience. For the kustomize dev path, CompiledDefaults() uses the local dev audience, which is correct for local dev clusters.

@esnible — DefaultSpiffeHelperConfig note (defaults.go:10)
Added a comment clarifying that the jwt_audience targets local dev Keycloak and that production/OpenShift deployments must override via Helm values. ✅

@pdettori — Idempotent path skips label reconciliation (agentruntime_controller.go:1099)
Great catch. Fixed: label reconciliation now happens before the content-equality check. A leftover CM from the old static-template path (labeled managed-by: kustomize) whose content matches the new default now gets its label adopted to managed-by: kagenti-operator. Added a new test case specifically for this label adoption scenario. ✅

@kevincogan kevincogan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice work addressing the earlier review feedback. The label adoption fix and the jwt_audience documentation update both look good to me.

One remaining thing I would like to check before approving: ensureSpiffeHelperConfigMap() now seems to be on the critical path for keeping PlatformConfig as the source of truth for spiffe-helper-config. If that create/update/check fails, reconcile currently logs/emits an event but then continues into config hash calculation and workload config application.

Would it be safer to return and requeue here, similar to the SCC setup path, so we do not risk stamping a workload using a stale or missing spiffe-helper-config?

I may be missing a reason this is intentionally best-effort, but I think it would be good to either make this fail/requeue or explicitly document why continuing is safe.

@r3v5
r3v5 force-pushed the absorb-spiffe-helper-config-into-global-kagenti-platform-config branch from 66f6c74 to e64b378 Compare June 22, 2026 09:10
r3v5 added a commit to r3v5/operator that referenced this pull request Jun 22, 2026
Remove spiffe-helper-config from templateConfigMapNames since it is now
derived from PlatformConfig via ensureSpiffeHelperConfigMap. Make failure
in ensureSpiffeHelperConfigMap set error status and requeue (30s), matching
the SCC binding pattern, to prevent stamping workloads with a stale or
missing spiffe-helper-config.

Addresses review feedback from kevincogan on PR rossoctl#437.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
r3v5 added a commit to r3v5/operator that referenced this pull request Jun 22, 2026
Remove spiffe-helper-config from templateConfigMapNames since it is now
derived from PlatformConfig via ensureSpiffeHelperConfigMap. Make failure
in ensureSpiffeHelperConfigMap set error status and requeue (30s), matching
the SCC binding pattern, to prevent stamping workloads with a stale or
missing spiffe-helper-config.

Addresses review feedback from kevincogan on PR rossoctl#437.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
@r3v5
r3v5 force-pushed the absorb-spiffe-helper-config-into-global-kagenti-platform-config branch from 28307e4 to 48ce0ef Compare June 22, 2026 09:42
r3v5 added a commit to r3v5/operator that referenced this pull request Jun 22, 2026
Remove spiffe-helper-config from templateConfigMapNames since it is now
derived from PlatformConfig via ensureSpiffeHelperConfigMap. Make failure
in ensureSpiffeHelperConfigMap set error status and requeue (30s), matching
the SCC binding pattern, to prevent stamping workloads with a stale or
missing spiffe-helper-config.

Addresses review feedback from kevincogan on PR rossoctl#437.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Ian Miller <milleryan2003@gmail.com>
@r3v5
r3v5 force-pushed the absorb-spiffe-helper-config-into-global-kagenti-platform-config branch from 48ce0ef to d597776 Compare June 22, 2026 09:45
@r3v5

r3v5 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Good catch — you're right, continuing past a failed ensureSpiffeHelperConfigMap risks stamping workloads that volume-mount a stale or missing CM.

Fixed in 48ce0ef: failure now calls updateErrorStatus with reason "SpiffeHelperConfigError" and returns ctrl.Result{RequeueAfter: 30 * time.Second}, matching the SCC binding pattern.

Also removed "spiffe-helper-config" from templateConfigMapNames — it was still listed there but shouldn't be since the controller now derives it via ensureSpiffeHelperConfigMap instead of template-copying.

@pdettori

Copy link
Copy Markdown
Member

Review Follow-up: All Comments Addressed

Checked the latest revision (commits 5–7) against the outstanding review feedback:

# Reviewer Comment Resolution
1 @esnible OpenShift kustomize path regression (deleted jwt_audience override) Override now via Helm defaults.spiffe.helperConfig; doc comment on DefaultSpiffeHelperConfig explicitly states prod/OpenShift must override
2 @esnible Nit: add note on DefaultSpiffeHelperConfig about prod override 4-line doc comment added to the constant
3 @pdettori Label reconciliation skipped by content-equality short-circuit Label checked before needsUpdate gate (commit 5), with dedicated test for leftover CM adoption
4 @kevincogan ensureSpiffeHelperConfigMap failure should requeue, not continue Returns RequeueAfter: 30s on error, matching SCC pattern (commit 7)

CI is all green (16/16 passing).

Remaining blocker: the branch has merge conflicts with main — needs a rebase before this can be merged.

Assisted-By: Claude Code

@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.

will re-evaluate after rebase

@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.

need to resolve conflicts

r3v5 added 7 commits June 23, 2026 14:42
Add HelperConfig string field to SpiffeConfig struct so PlatformConfig
can carry raw spiffe-helper helper.conf content. Wire default content
into CompiledDefaults() and log truncated snippet on startup.

RHAIENG-4939

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Ian Miller <milleryan2003@gmail.com>
Add GetPlatformConfig to AgentRuntimeReconciler and
ensureSpiffeHelperConfigMap() which creates/overwrites the
spiffe-helper-config CM per namespace from PlatformConfig. Remove
spiffe-helper-config from templateConfigMapNames (no longer
template-copied). Include helperConfig in config hash so changes
trigger rolling updates.

RHAIENG-4939

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Ian Miller <milleryan2003@gmail.com>
Webhook no longer reads spiffe-helper-config CM at admission time.
Remove SpiffeHelperConf field from NamespaceConfig and ResolvedConfig
structs. The controller now derives the CM from PlatformConfig; the
volume mount still references the CM by name (unchanged).

RHAIENG-4939

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Ian Miller <milleryan2003@gmail.com>
Add spiffe section with helperConfig to Helm values.yaml under defaults.
Delete static spiffe-helper-config.yaml template and OpenShift
spiffe-helper-keycloak.yaml kustomize patch. Update kustomization
references and e2e fixture comments.

RHAIENG-4939

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Ian Miller <milleryan2003@gmail.com>
Address PR review feedback:
- Label reconciliation now runs before content comparison so leftover
  CMs from the old static-template path get their managed-by label
  adopted even when content already matches PlatformConfig.
- Add prod/OpenShift override note to DefaultSpiffeHelperConfig.
- Add test for label adoption on leftover CMs.

RHAIENG-4939

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Ian Miller <milleryan2003@gmail.com>
Wrap AgentRuntime CR apply in Eventually with 1m timeout and 5s poll,
matching the retry pattern already used by authbridge and combined e2e
tests. Fixes flaky webhook connection-refused race when the validation
webhook TCP listener isn't accepting connections yet despite endpoint
being registered.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Ian Miller <milleryan2003@gmail.com>
Remove spiffe-helper-config from templateConfigMapNames since it is now
derived from PlatformConfig via ensureSpiffeHelperConfigMap. Make failure
in ensureSpiffeHelperConfigMap set error status and requeue (30s), matching
the SCC binding pattern, to prevent stamping workloads with a stale or
missing spiffe-helper-config.

Addresses review feedback from kevincogan on PR rossoctl#437.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Ian Miller <milleryan2003@gmail.com>
@r3v5
r3v5 force-pushed the absorb-spiffe-helper-config-into-global-kagenti-platform-config branch from d597776 to 661dd7f Compare June 23, 2026 13:46
@r3v5

r3v5 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

need to resolve conflicts

Merge conflicts have been resolved

@r3v5
r3v5 requested review from kevincogan and pdettori June 23, 2026 15:08

@kevincogan kevincogan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the rebase and for addressing the previous feedback. The main blocker I raised looks fixed now: ensureSpiffeHelperConfigMap() failures update status and requeue instead of continuing and potentially stamping workloads with stale or missing helper config.

One remaining thing I think is worth tightening before approval: after ensureSpiffeHelperConfigMap() creates or updates spiffe-helper-config, ComputeConfigHash() is still called with the cached client. Since that read may come from the controller-runtime cache, the hash could be computed from stale or missing helper.conf immediately after the update.

Could we make this deterministic by either computing the hash with the uncached reader/APIReader, or requeueing after creating/updating spiffe-helper-config before applying the workload config?

After that, assuming the latest checks are green, I’d be comfortable approving.

…nfig write

ensureSpiffeHelperConfigMap() creates/updates the CM via the API server,
but ComputeConfigHash() was reading it back through the controller-runtime
cache, which may not yet reflect the write. Switch to uncachedReader() so
the hash is always computed from the authoritative state.

Addresses review feedback from kevincogan on PR rossoctl#437.

RHAIENG-4939

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Ian Miller <milleryan2003@gmail.com>
@r3v5

r3v5 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Hey @kevincogan ! I have addressed your comment.

@r3v5

r3v5 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

FYI, sharing the latest manual testing in local Kind cluster

Verification: RHAIENG-4939 — Absorb spiffe-helper config into PlatformConfig

Date: 2026-06-24
PR: #437
Branch: absorb-spiffe-helper-config-into-global-kagenti-platform-config
Cluster: Kind (local), Kubernetes v1.35.0

Environment

  • macOS Darwin 25.5.0 (arm64)
  • podman 5.7.1 (podman-machine-default, 6 CPUs, 16 GiB RAM)
  • kind 0.31.0
  • helm v3.20.0
  • kubectl v1.35.2

1. Create Kind cluster

$ kind create cluster --name kagenti
enabling experimental podman provider
Creating cluster "kagenti" ...
 ✓ Ensuring node image (kindest/node:v1.35.0) 🖼
 ✓ Preparing nodes 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
Set kubectl context to "kind-kagenti"
You can now use your cluster with:

kubectl cluster-info --context kind-kagenti

Thanks for using kind! 😊

2. Build operator image with podman

$ cd kagenti-operator/
$ make docker-build CONTAINER_TOOL=podman
podman build -t controller:latest .
[1/2] STEP 1/11: FROM --platform=linux/arm64 docker.io/golang:1.26 AS builder
...
[1/2] STEP 11/11: RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -o manager cmd/main.go
--> 19b1156fbd03
[2/2] STEP 1/5: FROM gcr.io/distroless/static:nonroot
...
[2/2] STEP 5/5: ENTRYPOINT ["/manager"]
--> df2456ec77e8
Successfully tagged localhost/controller:latest
df2456ec77e8d...

3. Load image into Kind

The Makefile kind-load-image target expects localhost/kagenti-operator:<git-hash>, but docker-build uses IMG ?= controller:latest. Tagged and loaded via archive:

$ git rev-parse --short HEAD
661dd7f

$ podman tag localhost/controller:latest localhost/kagenti-operator:661dd7f

$ podman save localhost/kagenti-operator:661dd7f -o /tmp/kagenti-operator.tar
$ kind load image-archive /tmp/kagenti-operator.tar --name kagenti
enabling experimental podman provider

Verified image present on Kind node:

$ podman exec kagenti-control-plane crictl images | grep kagenti
localhost/kagenti-operator   661dd7f   df2456ec77e8d   82.7MB

4. Install cert-manager

$ kubectl --context kind-kagenti apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
namespace/cert-manager created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
...
deployment.apps/cert-manager-webhook created
mutatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
validatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
$ kubectl --context kind-kagenti wait --for=condition=Available -n cert-manager deployment/cert-manager-webhook --timeout=120s
deployment.apps/cert-manager-webhook condition met

5. Install operator via Helm

$ helm --kube-context kind-kagenti upgrade --install --create-namespace \
    -n kagenti-system kagenti-operator ../charts/kagenti-operator \
    --set controllerManager.container.image.repository=localhost/kagenti-operator \
    --set controllerManager.container.image.tag=661dd7f
Release "kagenti-operator" does not exist. Installing it now.
NAME: kagenti-operator
LAST DEPLOYED: Wed Jun 24 10:54:42 2026
NAMESPACE: kagenti-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
$ kubectl --context kind-kagenti wait --for=condition=Available -n kagenti-system deployment/kagenti-controller-manager --timeout=120s
deployment.apps/kagenti-controller-manager condition met

6. Verification: PlatformConfig CM contains spiffe.helperConfig

$ kubectl --context kind-kagenti get cm kagenti-platform-config -n kagenti-system -o yaml
apiVersion: v1
data:
  config.yaml: |
    images:
      authbridge: ghcr.io/kagenti/kagenti-extensions/authbridge:latest
      authbridgeLite: ghcr.io/kagenti/kagenti-extensions/authbridge-lite:latest
      envoyProxy: ghcr.io/kagenti/kagenti-extensions/authbridge-envoy:latest
      proxyInit: ghcr.io/kagenti/kagenti-extensions/proxy-init:latest
      pullPolicy: IfNotPresent
    proxy:
      adminPort: 9901
      allowedEgressEnforcement:
      - enforce-redirect
      - none
      inboundProxyPort: 15124
      iptablesCmd: ""
      port: 15123
      transparentPort: 8082
      uid: 1337
    resources:
      authbridge:
        limits:
          cpu: 300m
          memory: 384Mi
        requests:
          cpu: 100m
          memory: 128Mi
      envoyProxy:
        limits:
          cpu: 200m
          memory: 256Mi
        requests:
          cpu: 50m
          memory: 64Mi
      proxyInit:
        limits:
          cpu: 10m
          memory: 10Mi
        requests:
          cpu: 10m
          memory: 10Mi
    spiffe:
      helperConfig: |
        agent_address = "/spiffe-workload-api/spire-agent.sock"
        cmd = ""
        cmd_args = ""
        svid_file_name = "/opt/svid.pem"
        svid_key_file_name = "/opt/svid_key.pem"
        svid_bundle_file_name = "/opt/svid_bundle.pem"
        cert_file_mode = 0644
        key_file_mode = 0640
        jwt_svids = [{jwt_audience="http://keycloak.localtest.me:8080/realms/kagenti", jwt_svid_file_name="/opt/jwt_svid.token"}]
        jwt_svid_file_mode = 0644
        include_federated_domains = true
      socketPath: unix:///spiffe-workload-api/spire-agent.sock
      trustDomain: cluster.local
kind: ConfigMap
metadata:
  annotations:
    meta.helm.sh/release-name: kagenti-operator
    meta.helm.sh/release-namespace: kagenti-system
  labels:
    app.kubernetes.io/component: platform-defaults
    app.kubernetes.io/instance: kagenti-operator
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: kagenti-operator-chart
    app.kubernetes.io/version: 0.2.0-alpha.24
    helm.sh/chart: 0.2.0-alpha.24
  name: kagenti-platform-config
  namespace: kagenti-system

RESULT: PASSspiffe.helperConfig present in PlatformConfig CM with full helper.conf content.

7. Verification: AgentRuntime creates spiffe-helper-config CM in agent namespace

Created test resources:

$ kubectl --context kind-kagenti create namespace test-agent
namespace/test-agent created
$ kubectl --context kind-kagenti apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-agent
  namespace: test-agent
  labels:
    app: test-agent
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-agent
  template:
    metadata:
      labels:
        app: test-agent
    spec:
      containers:
      - name: agent
        image: busybox:latest
        command: ["sleep", "infinity"]
---
apiVersion: agent.kagenti.dev/v1alpha1
kind: AgentRuntime
metadata:
  name: test-agent-runtime
  namespace: test-agent
spec:
  type: agent
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: test-agent
EOF
deployment.apps/test-agent created
agentruntime.agent.kagenti.dev/test-agent-runtime created

Verified spiffe-helper-config CM:

$ kubectl --context kind-kagenti get cm spiffe-helper-config -n test-agent -o yaml
apiVersion: v1
data:
  helper.conf: |
    agent_address = "/spiffe-workload-api/spire-agent.sock"
    cmd = ""
    cmd_args = ""
    svid_file_name = "/opt/svid.pem"
    svid_key_file_name = "/opt/svid_key.pem"
    svid_bundle_file_name = "/opt/svid_bundle.pem"
    cert_file_mode = 0644
    key_file_mode = 0640
    jwt_svids = [{jwt_audience="http://keycloak.localtest.me:8080/realms/kagenti", jwt_svid_file_name="/opt/jwt_svid.token"}]
    jwt_svid_file_mode = 0644
    include_federated_domains = true
kind: ConfigMap
metadata:
  creationTimestamp: "2026-06-24T09:56:56Z"
  labels:
    app.kubernetes.io/managed-by: kagenti-operator
  name: spiffe-helper-config
  namespace: test-agent
  resourceVersion: "1234"
  uid: 9decf528-6c49-4f84-900e-8d6c2b89480b

RESULT: PASSspiffe-helper-config CM created in agent namespace with correct helper.conf content derived from PlatformConfig.

8. Verification: CM content matches PlatformConfig

PlatformConfig spiffe.helperConfig:

agent_address = "/spiffe-workload-api/spire-agent.sock"
cmd = ""
cmd_args = ""
svid_file_name = "/opt/svid.pem"
svid_key_file_name = "/opt/svid_key.pem"
svid_bundle_file_name = "/opt/svid_bundle.pem"
cert_file_mode = 0644
key_file_mode = 0640
jwt_svids = [{jwt_audience="http://keycloak.localtest.me:8080/realms/kagenti", jwt_svid_file_name="/opt/jwt_svid.token"}]
jwt_svid_file_mode = 0644
include_federated_domains = true

Namespace spiffe-helper-confighelper.conf:

agent_address = "/spiffe-workload-api/spire-agent.sock"
cmd = ""
cmd_args = ""
svid_file_name = "/opt/svid.pem"
svid_key_file_name = "/opt/svid_key.pem"
svid_bundle_file_name = "/opt/svid_bundle.pem"
cert_file_mode = 0644
key_file_mode = 0640
jwt_svids = [{jwt_audience="http://keycloak.localtest.me:8080/realms/kagenti", jwt_svid_file_name="/opt/jwt_svid.token"}]
jwt_svid_file_mode = 0644
include_federated_domains = true

RESULT: PASS — Content is identical.

9. Verification: Controller logs confirm PlatformConfig derivation

$ kubectl --context kind-kagenti logs -n kagenti-system deployment/kagenti-controller-manager --tail=30 | grep -i "spiffe-helper"
{"level":"info","ts":"2026-06-24T09:56:56Z","msg":"Created spiffe-helper-config from PlatformConfig","controller":"agentruntime","controllerGroup":"agent.kagenti.dev","controllerKind":"AgentRuntime","AgentRuntime":{"name":"test-agent-runtime","namespace":"test-agent"},"namespace":"test-agent","name":"test-agent-runtime","reconcileID":"93ea8dd9-ca5a-48ed-97e5-c9fcd70e4c31","namespace":"test-agent"}

RESULT: PASS — Log confirms CM was created from PlatformConfig (not copied from template).

10. Verification: Config-hash annotation applied to workload

$ kubectl --context kind-kagenti get deployment test-agent -n test-agent -o jsonpath='{.spec.template.metadata.annotations}'
{"kagenti.io/config-hash":"1a68ad029ad34c3f4895d5dc1731ef2153b769121ba9e90ad22d68da0abecf95","kagenti.io/mtls-mode":"permissive"}

RESULT: PASS — Config hash annotation present, meaning changes to helperConfig would trigger rolling update.

11. Verification: AgentRuntime status conditions

$ kubectl --context kind-kagenti get agentruntime -n test-agent -o yaml
status:
  conditions:
  - message: Deployment test-agent resolved
    reason: TargetFound
    status: "True"
    type: TargetResolved
  - message: Namespace test-agent enrolled in Istio ambient mesh
    reason: NamespaceLabeled
    status: "True"
    type: IstioMeshEnrolled
  - message: 'mTLS requires SPIRE; either deploy SPIRE or set mTLSMode: disabled'
    reason: SPIREUnavailable
    status: "False"
    type: MTLSReady
  - message: Configuration resolved successfully
    reason: ConfigResolved
    status: "True"
    type: ConfigResolved
  - message: Workload test-agent configured with config-hash 1a68ad029ad3
    reason: Configured
    status: "True"
    type: Ready
  configuredPods: 1
  observedGeneration: 1

RESULT: PASS — All conditions healthy. ConfigResolved=True, Ready=True. MTLSReady is False as expected (no SPIRE in Kind cluster).


Summary

# Check Result
1 kagenti-platform-config CM contains spiffe.helperConfig PASS
2 Creating AgentRuntime produces spiffe-helper-config CM in agent namespace PASS
3 CM content matches PlatformConfig PASS
4 Controller log confirms PlatformConfig derivation (not template copy) PASS
5 Config-hash annotation applied to workload (enables rolling updates) PASS
6 AgentRuntime status conditions healthy PASS

Overall: ALL CHECKS PASS

The spiffe-helper-config is correctly derived from PlatformConfig at runtime, no longer copied from static templates. Changes to defaults.spiffe.helperConfig in Helm values will propagate to all agent namespaces and trigger rolling updates via config-hash.

@kevincogan kevincogan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the follow-up fix. I rechecked the latest revision against main, and my remaining concern looks addressed now: ComputeConfigHash() uses the uncached reader after ensureSpiffeHelperConfigMap(), so the hash should reflect the freshly written spiffe-helper-config.

The other items I was tracking also look covered, and the PlatformConfig source-of-truth design looks clean.

This looks good to me from my end.

@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.

Approving — my prior feedback is fully addressed and conflicts are resolved.

  • Label reconciliation suggestion: ensureSpiffeHelperConfigMap now reconciles the kagenti.io/managed-by ownership label before the content-equality short-circuit, so a leftover CM with matching content but a missing/stale label still gets adopted. ✓
  • Conflicts: rebased, branch is MERGEABLE. ✓
  • Also confirmed @kevincogan's items: failure now requeues (RequeueAfter: 30s) and ComputeConfigHash uses the uncached reader, so the hash reflects the freshly-written spiffe-helper-config. ✓

All 16 checks green (E2E + Integration included). PlatformConfig source-of-truth design looks clean.

Non-blocking note for follow-up: deleting config/openshift/patches/spiffe-helper-keycloak.yaml is safe only if OpenShift is now Helm-only; if the kustomize overlay is still a supported install path, the in-cluster jwt_audience override falls back to the public dev default. This was @esnible's earlier (non-blocking) point and is documented in defaults.go now — flagging so it's not lost.

Assisted-By: Claude Code

@pdettori
pdettori merged commit c19b079 into rossoctl:main Jun 24, 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.

4 participants