Skip to content

feat: create SharedTrust controller for cert-manager cacerts reconciliation#383

Merged
pdettori merged 8 commits into
rossoctl:mainfrom
r3v5:shared-trust-controller
Jun 2, 2026
Merged

feat: create SharedTrust controller for cert-manager cacerts reconciliation#383
pdettori merged 8 commits into
rossoctl:mainfrom
r3v5:shared-trust-controller

Conversation

@r3v5

@r3v5 r3v5 commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add event-driven SharedTrust controller that watches cert-manager Certificate objects and reconciles cacerts
    secrets for Istio mTLS in istio-system and openshift-ingress
  • On cert data changes, trigger rollout restarts of istiod, istiod-openshift-gateway, and ztunnel so they reload
    the new CA chain
  • Verify root CA fingerprints match intermediate CA issuers; delete stale intermediates to force cert-manager
    re-issuance on mismatch
  • Controller registers conditionally — skips startup if cert-manager CRDs are absent

Jira

RHAIENG-4901

Next steps

When current PR is merged, I will submit this change kagenti/scripts/ocp/setup-kagenti.sh (remove operator-owned steps from _ensure_rhoai_shared_trust; retain cluster-admin/static install steps)

Test plan

  • Unit tests: fingerprint verification, cacerts assembly, all reconcile paths (fake client)
  • E2E tests: Kind cluster with cert-manager — cacerts creation, rebuild after deletion, restart annotations,
    graceful missing workload handling
  • Manual verification on Kind with Istio ambient mode — full mTLS connectivity confirmed
  • Manual verification on OpenShift (ROSA) with OSSM3 — cacerts created, fingerprints match, istiod loads our
    CA chain, ztunnel mTLS with SPIFFE identities, cert rotation triggers rebuild
  • Run: go test ./internal/controller/ -run "TestReconcile_|TestVerifyCA|TestBuildCacerts" -v
  • Run: export KIND_EXPERIMENTAL_PROVIDER=podman && CONTAINER_TOOL=podman KIND_CLUSTER=kagenti go test ./test/e2e/ -v -count=1 -timeout 20m --ginkgo.focus="SharedTrust"
  • Manual verification on OpenShift 4.19.18 (ROSA) with RHOAI 3.4.0, OSSM3 v3.2.2, Istio v1.27.5

@r3v5
r3v5 requested a review from a team as a code owner May 28, 2026 13:09
odh-devops-app Bot pushed a commit to opendatahub-io/agents-operator that referenced this pull request May 29, 2026
…n synthesizePipeline

## Why

When a namespace's `authbridge-runtime-config` ConfigMap has no
`pipeline:` of its own, the webhook synthesizes one from the
namespace's `authbridge-config` env-var contract (NamespaceConfig).
The synthesis passed `keycloak_url` + `keycloak_realm` only to the
outbound token-exchange plugin. jwt-validation got just `issuer`.

kagenti-extensions#383 extends the jwt-validation plugin to accept
these two fields and derive jwks_url from the INTERNAL Keycloak
URL — the sidecar actually reaches the JWKS endpoint from inside
the cluster, and `issuer` is the PUBLIC hostname (required for
`iss`-claim matching but typically unreachable from inside the
pod). Without the keycloak_* hints, jwt-validation falls back to
issuer-derivation and every inbound request fails with
"connection refused" fetching the JWKS → 401.

## Scope

Pure addition to `synthesizePipeline`: the same two fields that
already feed token-exchange now also feed jwt-validation. Both
plugins end up with the same "where is Keycloak internally" hint,
mirroring the pre-PR-rossoctl#378 binary behavior where jwks_url was
derived from outbound.token_url via a cross-plugin pass.

Test: extend `TestEnsurePerAgentConfigMap_EmptyBaseYAML_Fallback
FromNsConfig` to assert jwt-validation now receives keycloak_url
and keycloak_realm.

## Compatibility

Requires kagenti-extensions ≥ the tag that includes rossoctl#383 — older
authbridge binaries reject the new fields at DisallowUnknownFields
decode. The chart in rossoctl/rossoctl#1507 bumps both pins together.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Hai Huang <huang195@gmail.com>
@cwiklik
cwiklik self-requested a review May 29, 2026 14:12

@cwiklik cwiklik left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Well-structured PR with clean commit history, good test coverage (unit + e2e), and proper conditional registration pattern. The fingerprint verification logic is correct — it compares the root CA cert against the ca.crt field (issuing CA) stored in the intermediate's secret, validating the trust chain. All CI checks pass.

Areas reviewed: Go (controller logic, helpers, tests), YAML (RBAC)
Commits: 6 commits, all signed-off: yes
CI status: all 15 checks passing

Comment thread kagenti-operator/internal/controller/sharedtrust_controller.go
Comment thread kagenti-operator/internal/controller/sharedtrust_controller.go
Comment thread kagenti-operator/internal/controller/sharedtrust_controller.go Outdated
Comment thread kagenti-operator/internal/controller/sharedtrust_helpers.go
r3v5 added 6 commits June 2, 2026 12:16
Add github.com/cert-manager/cert-manager for typed access to
Certificate and ClusterIssuer resources.

Ref: RHAIENG-4901

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

Pure functions with no Kubernetes dependency:
- verifyCAFingerprint: SHA256 comparison of root vs intermediate CA
- buildCacertsData: assembles Istio cacerts secret key mapping

Includes unit tests for matching/mismatching fingerprints, invalid
PEM/DER, correct key mapping, and cert-chain concatenation.

Ref: RHAIENG-4901

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Ian Miller <milleryan2003@gmail.com>
Event-driven controller watching cert-manager Certificate objects to
reconcile cacerts secrets for Istio mTLS. Creates cacerts in
istio-system and openshift-ingress with the key format istiod expects.

Triggers rollout restarts of istiod and ztunnel when cert data changes.
Verifies root CA fingerprints match intermediate issuers; deletes stale
intermediates to force re-issuance on mismatch.

Registers conditionally via CertManagerCRDExists, following the
TektonConfig pattern.

Ref: RHAIENG-4901

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Ian Miller <milleryan2003@gmail.com>
Tests with fake client covering certificates missing, not ready,
fingerprint mismatch, happy path cacerts creation, data change update,
and no-restart when unchanged.

Ref: RHAIENG-4901

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Ian Miller <milleryan2003@gmail.com>
End-to-end tests on Kind cluster with cert-manager covering cacerts
creation, rebuild after intermediate deletion, restart annotations on
istiod and ztunnel, and graceful handling of missing workloads.

Ref: RHAIENG-4901

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Ian Miller <milleryan2003@gmail.com>
- Fix gofmt alignment in const block
- Replace deprecated result.Requeue with RequeueAfter
- Suppress deprecated GetEventRecorderFor (repo-wide, not just ours)
- Break long lines in e2e test to stay under 120 chars

Ref: RHAIENG-4901

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Ian Miller <milleryan2003@gmail.com>
@r3v5
r3v5 force-pushed the shared-trust-controller branch from fd0670b to 64252a2 Compare June 2, 2026 11:17
- Add Secret watch on cacerts to self-heal from manual drift
- Replace time.Sleep in CertManagerCRDExists with wait.PollUntilContextTimeout
  for cleaner cancellation semantics during operator shutdown
- Document certFingerprint first-PEM-block assumption
- Document hardcoded config values match RHOAI/OSSM3 layout

Ref: RHAIENG-4901

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

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

CI failure analysis: The E2E failure is in the pre-existing "Combined AgentRuntime + AgentCard + Auth Bridge E2E" test, NOT in the SharedTrust code (all 4 SharedTrust tests pass).

Root cause: The controller-manager pod never materializes on re-deploy (second cycle). The test harness runs make undeploymake uninstall → re-creates namespace → make deploy, but with cert-manager now installed in the cluster (for SharedTrust E2E), namespace termination is slower. The re-create races against the still-terminating namespace, and the pod is never scheduled. Evidence: 120s of polling returns empty ("No resources found in kagenti-operator-system namespace").

This is a test-infra race exposed by cert-manager's presence, not a code defect.

Areas reviewed: Go (controller, helpers, scheme registration, RBAC), CI logs (full E2E timeline analysis)
Commits: 6 commits, all signed-off: yes
CI status: 14/15 passing; E2E failure is unrelated test-infra race

Assisted-By: Claude Code

Comment thread kagenti-operator/cmd/main.go
Replace polling loop with kubectl wait --for=delete when a namespace
is still Terminating during re-deploy. With cert-manager installed,
namespace finalizers take longer during teardown between test suites,
causing a race where namespace creation silently fails.

Also increase ensureNamespaceReady timeout from 60s to 120s.

Ref: RHAIENG-4901

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Ian Miller <milleryan2003@gmail.com>
@r3v5
r3v5 force-pushed the shared-trust-controller branch from 4f6a276 to 7d51a33 Compare June 2, 2026 14:10
@pdettori
pdettori merged commit 359c3bf into rossoctl:main Jun 2, 2026
15 checks passed
@github-project-automation github-project-automation Bot moved this from New /:ToDo to Done in Kagenti Issue Prioritization Jun 2, 2026
@r3v5

r3v5 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

I have submitted follow up PR refactor(ocp-installer): remove operator-owned shared-trust steps for ocp script.

odh-devops-app Bot pushed a commit to opendatahub-io/agents-operator that referenced this pull request Jun 6, 2026
…terRole

The Helm chart's hand-maintained ClusterRole (charts/kagenti-operator/
templates/rbac/role.yaml) drifted from the kubebuilder-generated
config/rbac/role.yaml: it was missing the cert-manager.io/certificates +
clusterissuers get/list/watch rules.

PR rossoctl#383 (SharedTrust controller) added a Watches(&cmv1.Certificate{}) informer
and the corresponding +kubebuilder:rbac marker, and regenerated
config/rbac/role.yaml — but did not update the chart's separate ClusterRole.
Since installs render RBAC from the chart, the deployed operator's
ServiceAccount cannot list/watch cert-manager Certificates. The Certificate
informer never syncs, the manager misses its cache-sync deadline and exits 1
(~2.5 min crashloop), so NO controllers run — including
ClientRegistrationReconciler. Agents' per-agent Keycloak client-credentials
secrets are therefore never created, and agent pods hang on FailedMount
(observed on weather-agent; matches a user report on a fresh main install).

Add the cert-manager.io rules to the chart role to match the generated role.

Follow-up (not in this PR): the chart ClusterRole is maintained separately from
config/rbac/role.yaml with no sync, so any future RBAC marker can silently
drift again. Worth generating the chart role from the kubebuilder role or
adding a CI drift check.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Hai Huang <huang195@gmail.com>
varshaprasad96 added a commit to varshaprasad96/kagenti-operator that referenced this pull request Jun 10, 2026
Update mTLS config delivery from ConfigMap injection to annotation +
env var approach per PR rossoctl#405 team review:

- Controller sets kagenti.io/mtls-mode annotation on pod template
  (triggers rolling restart on change, independent of config hash)
- Webhook reads mTLSMode from AgentRuntime CR at pod CREATE time,
  sets MTLS_MODE env var on authbridge container
- Acknowledge Istio mTLS coexistence (PR rossoctl#383, rossoctl#399, RHAIENG-5467)
- Mark T005 as SUPERSEDED with new annotation-based approach
- Update contracts, data-model, research, REVIEWERS.md

Jira: RHAIENG-4944

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants