Feature Description
Summary
Extend the admission webhook to look up an AgentRuntime CR at Pod CREATE time and merge its configuration with platform defaults. This gives developers per-workload control over identity and observability settings without modifying the webhook's ConfigMap defaults.
Today the webhook reads configuration exclusively from the kagenti-webhook-defaults ConfigMap. With this change, the webhook resolves the Pod's owner workload, looks up a matching AgentRuntime CR, and merges the CR's spec fields over the ConfigMap defaults. If no AgentRuntime CR exists, the webhook continues to use ConfigMap defaults — no behavior change for existing workloads.
Context
The AgentRuntime CRD and controller are implemented in kagenti-operator. The controller applies kagenti.io/type labels and kagenti.io/config-hash annotations to the Deployment's PodTemplateSpec, which triggers a rolling update. The webhook intercepts the resulting Pod CREATE and needs the AgentRuntime CR to determine per-workload configuration.
Current config architecture
The webhook has a well-structured config system:
config.PlatformConfig (internal/webhook/config/types.go) — struct covering images, proxy settings, resources, token exchange, SPIFFE, observability, and per-sidecar defaults
config.ConfigLoader (internal/webhook/config/loader.go) — loads from a YAML file (mounted from ConfigMap), watches for changes, hot-reloads with debounce
injector.PrecedenceEvaluator (internal/webhook/injector/precedence.go) — multi-layer precedence chain: feature gates → namespace labels → workload labels → CR overrides (stub) → platform defaults
injector.PodMutator (internal/webhook/injector/pod_mutator.go) — applies the injection decision to the PodSpec using a ContainerBuilder
The PrecedenceEvaluator already accepts a tokenExchangeOverrides *TokenExchangeOverrides parameter — this is the designed extension point for CR-based overrides, currently passed as nil.
Proposed Solution
Required changes
1. Owner chain resolution — Pod to Deployment
File: new file, e.g. internal/webhook/resolver/owner_chain.go
The webhook receives a Pod admission request but the AgentRuntime CR's targetRef points to a Deployment (or StatefulSet). The webhook needs to walk the owner chain to find the workload:
2. AgentRuntime CR lookup
File: new file, e.g. internal/webhook/resolver/agentruntime_resolver.go
3. AgentRuntime informer cache
File: cmd/main.go or webhook setup
4. Config merging — AgentRuntime spec over PlatformConfig
File: new file, e.g. internal/webhook/config/merge.go
Merge AgentRuntime CR spec fields over the PlatformConfig loaded from the ConfigMap. The AgentRuntime CR is the higher-priority source — its fields override defaults where present.
5. Wire it into the injection path
File: internal/webhook/injector/pod_mutator.go, internal/webhook/v1alpha1/authbridge_webhook.go
6. Tests
Files: new test files + updates to existing tests
7. Documentation
Progress tracker
Phase 1: Resolution and lookup
Phase 2: Config merging
Phase 3: Integration and docs
Dependencies
| Dependency |
Repo |
Notes |
| AgentRuntime CRD definition |
kagenti-operator |
Need the Go types or CRD schema to deserialize the CR |
| Webhook targets Pods at CREATE time |
kagenti-extensions (this repo) |
Owner chain resolution only works when the webhook receives Pod objects |
Design decisions
Import vs. lightweight type: The webhook can either import the AgentRuntime types from the kagenti-operator Go module, or define a lightweight local struct with only the fields it needs. Importing ensures type consistency but adds a module dependency. A lightweight local type keeps the webhook independent but may drift. Recommend: import from kagenti-operator — the types are stable (v1alpha1) and the dependency is one-directional.
Informer vs. per-request API call: Use an informer cache. Per-request API calls add latency to every Pod admission (the webhook has a 10-second timeout). AgentRuntime CRs are small and low-cardinality — the informer memory cost is negligible.
Merge semantics: Field-level override, not full replacement. If the AgentRuntime CR specifies spec.identity.spiffe.trustDomain but not spec.trace.endpoint, the trust domain is overridden and the trace endpoint keeps the ConfigMap default. This matches the existing PlatformConfig overlay pattern used by the ConfigLoader (YAML file overlays compiled defaults).
Want to contribute?
Additional Context
No response
Feature Description
Summary
Extend the admission webhook to look up an
AgentRuntimeCR at Pod CREATE time and merge its configuration with platform defaults. This gives developers per-workload control over identity and observability settings without modifying the webhook's ConfigMap defaults.Today the webhook reads configuration exclusively from the
kagenti-webhook-defaultsConfigMap. With this change, the webhook resolves the Pod's owner workload, looks up a matching AgentRuntime CR, and merges the CR's spec fields over the ConfigMap defaults. If no AgentRuntime CR exists, the webhook continues to use ConfigMap defaults — no behavior change for existing workloads.Context
The AgentRuntime CRD and controller are implemented in kagenti-operator. The controller applies
kagenti.io/typelabels andkagenti.io/config-hashannotations to the Deployment's PodTemplateSpec, which triggers a rolling update. The webhook intercepts the resulting Pod CREATE and needs the AgentRuntime CR to determine per-workload configuration.Current config architecture
The webhook has a well-structured config system:
config.PlatformConfig(internal/webhook/config/types.go) — struct covering images, proxy settings, resources, token exchange, SPIFFE, observability, and per-sidecar defaultsconfig.ConfigLoader(internal/webhook/config/loader.go) — loads from a YAML file (mounted from ConfigMap), watches for changes, hot-reloads with debounceinjector.PrecedenceEvaluator(internal/webhook/injector/precedence.go) — multi-layer precedence chain: feature gates → namespace labels → workload labels → CR overrides (stub) → platform defaultsinjector.PodMutator(internal/webhook/injector/pod_mutator.go) — applies the injection decision to the PodSpec using aContainerBuilderThe
PrecedenceEvaluatoralready accepts atokenExchangeOverrides *TokenExchangeOverridesparameter — this is the designed extension point for CR-based overrides, currently passed asnil.Proposed Solution
Required changes
1. Owner chain resolution — Pod to Deployment
File: new file, e.g.
internal/webhook/resolver/owner_chain.goThe webhook receives a Pod admission request but the AgentRuntime CR's
targetRefpoints to a Deployment (or StatefulSet). The webhook needs to walk the owner chain to find the workload:pod.OwnerReferencesto find the ReplicaSet (for Deployments) or StatefulSetOwnerReferencesto find the Deployment{apiVersion, kind, name, namespace}2. AgentRuntime CR lookup
File: new file, e.g.
internal/webhook/resolver/agentruntime_resolver.goapi/v1alpha1) for deserializing the CRspec.targetRefmatches the resolved workload (apiVersion, kind, name)agent.kagenti.devgroup,v1alpha1version)3. AgentRuntime informer cache
File:
cmd/main.goor webhook setup4. Config merging — AgentRuntime spec over PlatformConfig
File: new file, e.g.
internal/webhook/config/merge.goMerge AgentRuntime CR spec fields over the
PlatformConfigloaded from the ConfigMap. The AgentRuntime CR is the higher-priority source — its fields override defaults where present.MergeWithAgentRuntime(base *PlatformConfig, cr *AgentRuntime) *PlatformConfigspec.identity.spiffe.trustDomain→ overridesPlatformConfig.Spiffe.TrustDomainspec.identity.clientRegistration.realm→ affects token exchange URL and client registration configspec.identity.clientRegistration.adminCredentialsSecret→ overrides the secret used for Keycloak admin accessspec.trace.endpoint→ overrides OTEL exporter endpoint (passed as env var to sidecar)spec.trace.protocol→ overrides OTEL protocolspec.trace.sampling.rate→ overrides sampling rate*PlatformConfig— do not mutate the shared base config5. Wire it into the injection path
File:
internal/webhook/injector/pod_mutator.go,internal/webhook/v1alpha1/authbridge_webhook.goHandle()), after decoding the Pod:PodMutatorPodMutator.InjectAuthBridge()to accept an optional merged config (or update itsGetPlatformConfigto return the merged version for this request)PrecedenceEvaluatorcall: populate thetokenExchangeOverridesparameter from the AgentRuntime CR (replaces the currentnilstub)ContainerBuilderif needed: it currently takes a*PlatformConfigat construction time — may need per-request config for merged values6. Tests
Files: new test files + updates to existing tests
make testpasses7. Documentation
ARCHITECTURE.mdto document the config merging flowProgress tracker
Phase 1: Resolution and lookup
Phase 2: Config merging
Phase 3: Integration and docs
make testpassesDependencies
Design decisions
Import vs. lightweight type: The webhook can either import the AgentRuntime types from the kagenti-operator Go module, or define a lightweight local struct with only the fields it needs. Importing ensures type consistency but adds a module dependency. A lightweight local type keeps the webhook independent but may drift. Recommend: import from kagenti-operator — the types are stable (v1alpha1) and the dependency is one-directional.
Informer vs. per-request API call: Use an informer cache. Per-request API calls add latency to every Pod admission (the webhook has a 10-second timeout). AgentRuntime CRs are small and low-cardinality — the informer memory cost is negligible.
Merge semantics: Field-level override, not full replacement. If the AgentRuntime CR specifies
spec.identity.spiffe.trustDomainbut notspec.trace.endpoint, the trust domain is overridden and the trace endpoint keeps the ConfigMap default. This matches the existingPlatformConfigoverlay pattern used by the ConfigLoader (YAML file overlays compiled defaults).Want to contribute?
Additional Context
No response