Skip to content
Merged
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
28 changes: 22 additions & 6 deletions charts/kagenti-operator/crds/agent.kagenti.dev_agentcards.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@
jsonPath: .status.card.name
name: Agent
type: string
- description: Signature Verified
jsonPath: .status.validSignature
- description: Identity Verified
jsonPath: .status.conditions[?(@.type=='Verified')].status
name: Verified
type: boolean
type: string
- description: Identity Bound
jsonPath: .status.bindingStatus.bound
name: Bound
Expand All @@ -53,10 +53,18 @@
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
- description: Attested Agent SPIFFE ID
jsonPath: .status.attestedAgentSpiffeId
name: AttestedAgent
priority: 1
type: string
name: v1alpha1
schema:
openAPIV3Schema:
description: AgentCard is the Schema for the agentcards API.
description: |-
AgentCard binds an A2A agent card to a backing workload. The controller periodically
fetches the card from the referenced workload, verifies its JWS signature and SPIFFE
identity against the configured trust domain, and caches the result in status.
properties:
apiVersion:
description: |-
Expand Down Expand Up @@ -84,8 +92,11 @@
strict:
default: false
description: |-
Strict enables enforcement mode: binding failures trigger network isolation.
When false (default), results are recorded in status only (audit mode).
Strict controls whether binding failures trigger enforcement actions
(label removal, restrictive NetworkPolicy).
When true, binding failure removes the verified label and applies restrictive NetworkPolicy.
When false (default), binding results are recorded in status only;
the workload retains its verified label and permissive policy.
type: boolean
trustDomain:
description: |-
Expand Down Expand Up @@ -126,6 +137,11 @@
status:
description: AgentCardStatus defines the observed state of AgentCard.
properties:
attestedAgentSpiffeId:
description: |-
AttestedAgentSpiffeID is the SPIFFE ID extracted from the agent's TLS peer certificate
during authenticated (mTLS) fetch. Set only when verifiedFetch is enabled and successful.
type: string
bindingStatus:
description: BindingStatus contains the result of identity binding
evaluation
Expand Down Expand Up @@ -349,7 +365,7 @@
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.

Check warning on line 368 in charts/kagenti-operator/crds/agent.kagenti.dev_agentcards.yaml

View workflow job for this annotation

GitHub Actions / YAML Lint

368:151 [line-length] line too long (162 > 150 characters)
format: date-time
type: string
message:
Expand All @@ -361,7 +377,7 @@
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date

Check warning on line 380 in charts/kagenti-operator/crds/agent.kagenti.dev_agentcards.yaml

View workflow job for this annotation

GitHub Actions / YAML Lint

380:151 [line-length] line too long (162 > 150 characters)
with respect to the current state of the instance.
format: int64
minimum: 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@
name: v1alpha1
schema:
openAPIV3Schema:
description: AgentRuntime is the Schema for the agentruntimes API.
description: |-
AgentRuntime attaches runtime configuration to a backing workload classified as an
agent or tool, providing per-workload overrides for SPIFFE identity and OpenTelemetry
tracing. The controller reports pod configuration coverage and phase in status.
properties:
apiVersion:
description: |-
Expand All @@ -58,6 +61,39 @@
spec:
description: AgentRuntimeSpec defines the desired state of AgentRuntime.
properties:
authBridgeMode:
description: |-
AuthBridgeMode selects the deployment shape for this workload's
authbridge sidecar. When unset, the namespace-level
authbridge-runtime-config ConfigMap's mode is used; if that is
also unset, the operator falls back to "proxy-sidecar".

Four valid values:

proxy-sidecar HTTP_PROXY env + authbridge-proxy (full plugin
set, including a2a/mcp/inference parsers) +
spiffe-helper bundled. No Envoy, no iptables.
Default mode.
envoy-sidecar Envoy + ext_proc authbridge + spiffe-helper
bundled. Requires the proxy-init iptables
container.
lite Same listener layout as proxy-sidecar but uses
the authbridge-lite image (jwt-validation +
token-exchange only, parsers dropped to shrink
the binary). For size-constrained deployments
that don't need protocol-aware abctl events.
waypoint Standalone deployment, not injected as a
sidecar. Used by Istio ambient mesh.

Set this when a single workload needs a different shape than the
namespace default. Most deployments leave it unset and let the
namespace ConfigMap drive the choice.
enum:
- proxy-sidecar
- envoy-sidecar
- lite
- waypoint
type: string
identity:
description: Identity specifies optional per-workload identity overrides
properties:
Expand All @@ -72,6 +108,37 @@
type: string
type: object
type: object
mtlsMode:
description: |-
MTLSMode selects the mTLS posture between authbridge sidecars on
the proxy-sidecar / lite paths. envoy-sidecar handles transport
security through Envoy SDS, which is currently not configured by
the kagenti envoy-config — admission rejects mtlsMode != disabled
when authBridgeMode is envoy-sidecar (tracked as a follow-up).

Three valid values:

disabled Plaintext between sidecars (default).
permissive Inbound: byte-peek listener accepts both TLS and
plaintext on the same port. Outbound: tries TLS,
falls back to plaintext on handshake failure (one-line
WARN log per fallback). Use during rollout.
strict Inbound: TLS-only, plaintext callers closed at
accept. Outbound: TLS-or-fail. Use after rollout
completes.

Resolution: AgentRuntime CR > namespace authbridge-runtime-config
mtls.mode > "disabled". Setting mtlsMode != disabled implicitly
requires SPIRE — the operator auto-enables spire for the workload.

Note: changing mtlsMode triggers a pod rollout because authbridge
cannot hot-reload mTLS config (the byte-peek listener is wired at
process start).
enum:
- disabled
- permissive
- strict
type: string
targetRef:
description: TargetRef identifies the workload backing this agent
runtime (duck typing).
Expand Down Expand Up @@ -140,7 +207,7 @@
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.

Check warning on line 210 in charts/kagenti-operator/crds/agent.kagenti.dev_agentruntimes.yaml

View workflow job for this annotation

GitHub Actions / YAML Lint

210:151 [line-length] line too long (162 > 150 characters)
format: date-time
type: string
message:
Expand All @@ -152,7 +219,7 @@
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date

Check warning on line 222 in charts/kagenti-operator/crds/agent.kagenti.dev_agentruntimes.yaml

View workflow job for this annotation

GitHub Actions / YAML Lint

222:151 [line-length] line too long (162 > 150 characters)
with respect to the current state of the instance.
format: int64
minimum: 0
Expand Down
19 changes: 19 additions & 0 deletions kagenti-operator/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,25 @@ help: ## Display this help.
.PHONY: manifests
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
$(CONTROLLER_GEN) rbac:roleName=manager-role crd:allowDangerousTypes=true webhook paths="./..." output:crd:artifacts:config=config/crd/bases
$(MAKE) sync-chart-crds

# sync-chart-crds keeps charts/kagenti-operator/crds/ in lockstep with
# config/crd/bases/. Helm's crds/ directory is the install path consumers
# get when they `helm install`; without this sync the chart copy drifts
# silently every time a CRD field is added (which has happened multiple
# times before — commits 677f63d, 21a2258, 4eb62af). Runs as the final
# step of `make manifests` so the chart copy reflects the freshly
# regenerated controller-gen output. Skips the stub `_.yaml` file
# controller-gen sometimes emits.
.PHONY: sync-chart-crds
sync-chart-crds: ## Mirror config/crd/bases/ into charts/kagenti-operator/crds/.
@for f in $$(ls config/crd/bases/*.yaml 2>/dev/null); do \
base=$$(basename $$f); \
case $$base in \
_.yaml) ;; \
*) cp $$f ../charts/kagenti-operator/crds/$$base ;; \
esac; \
done

.PHONY: generate
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
Expand Down
36 changes: 36 additions & 0 deletions kagenti-operator/api/v1alpha1/agentruntime_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,42 @@ type AgentRuntimeSpec struct {
// +optional
// +kubebuilder:validation:Enum=proxy-sidecar;envoy-sidecar;lite;waypoint
AuthBridgeMode string `json:"authBridgeMode,omitempty"`

// MTLSMode selects the mTLS posture between authbridge sidecars on
// the proxy-sidecar / lite paths. envoy-sidecar handles transport
// security through Envoy SDS, which is currently not configured by
// the kagenti envoy-config — admission rejects mtlsMode != disabled
// when authBridgeMode is envoy-sidecar (tracked as a follow-up).
//
// Three valid values:
//
// disabled Plaintext between sidecars (default).
// permissive Inbound: byte-peek listener accepts both TLS and
// plaintext on the same port. Outbound: tries TLS,
// falls back to plaintext on handshake failure (one-line
// WARN log per fallback). Use during rollout.
// strict Inbound: TLS-only, plaintext callers closed at
// accept. Outbound: TLS-or-fail. Use after rollout
// completes.
//
// Resolution: AgentRuntime CR > namespace authbridge-runtime-config
// mtls.mode > "disabled". Setting mtlsMode != disabled implicitly
// requires SPIRE — the operator auto-enables spire for the workload.
//
// CR-empty vs CR="disabled" are observably different in
// `kubectl get agentruntime -o yaml` (the former omits the field,
// the latter shows mtlsMode: disabled) but produce the same
// effective mode: empty falls through to the namespace ConfigMap,
// "disabled" is an explicit override that pins mode off even when
// the namespace default is non-disabled.
//
// Note: changing mtlsMode triggers a pod rollout because authbridge
// cannot hot-reload mTLS config (the byte-peek listener is wired at
// process start).
//
// +optional
// +kubebuilder:validation:Enum=disabled;permissive;strict
MTLSMode string `json:"mtlsMode,omitempty"`
}

// IdentitySpec configures workload identity for an AgentRuntime.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,37 @@ spec:
type: string
type: object
type: object
mtlsMode:
description: |-
MTLSMode selects the mTLS posture between authbridge sidecars on
the proxy-sidecar / lite paths. envoy-sidecar handles transport
security through Envoy SDS, which is currently not configured by
the kagenti envoy-config — admission rejects mtlsMode != disabled
when authBridgeMode is envoy-sidecar (tracked as a follow-up).

Three valid values:

disabled Plaintext between sidecars (default).
permissive Inbound: byte-peek listener accepts both TLS and
plaintext on the same port. Outbound: tries TLS,
falls back to plaintext on handshake failure (one-line
WARN log per fallback). Use during rollout.
strict Inbound: TLS-only, plaintext callers closed at
accept. Outbound: TLS-or-fail. Use after rollout
completes.

Resolution: AgentRuntime CR > namespace authbridge-runtime-config
mtls.mode > "disabled". Setting mtlsMode != disabled implicitly
requires SPIRE — the operator auto-enables spire for the workload.

Note: changing mtlsMode triggers a pod rollout because authbridge
cannot hot-reload mTLS config (the byte-peek listener is wired at
process start).
enum:
- disabled
- permissive
- strict
type: string
targetRef:
description: TargetRef identifies the workload backing this agent
runtime (duck typing).
Expand Down
48 changes: 46 additions & 2 deletions kagenti-operator/internal/controller/agentruntime_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ const (

// LabelNamespaceDefaults identifies namespace-level defaults ConfigMaps.
LabelNamespaceDefaults = "kagenti.io/defaults"

// AuthBridgeRuntimeConfigMapName is the namespace-scoped ConfigMap that
// holds the authbridge runtime config (config.yaml). Edits to this
// ConfigMap are watched by AgentRuntimeReconciler so the resolved-config
// hash picks them up and rolls affected workloads.
AuthBridgeRuntimeConfigMapName = "authbridge-runtime-config"
)

// resolvedConfig is the canonical representation used for hash computation.
Expand All @@ -58,6 +64,31 @@ type resolvedConfig struct {
Trace *traceConfig `json:"trace,omitempty"`
FeatureGates map[string]string `json:"featureGates,omitempty"`
Defaults map[string]string `json:"defaults,omitempty"`

// AuthBridgeMode and MTLSMode change the injected sidecar shape /
// transport posture, both of which require a pod restart to take
// effect. Including them here folds CR-edit changes into the
// config-hash so applyWorkloadConfig stamps a new hash on the pod
// template and the Deployment rolls.
AuthBridgeMode string `json:"authBridgeMode,omitempty"`
MTLSMode string `json:"mtlsMode,omitempty"`

// AuthBridgeRuntime captures the namespace authbridge-runtime-config
// ConfigMap's config.yaml content so namespace-level edits flow into
// the hash. Stored as the raw string (not parsed) because authbridge
// pipelines/listener/mtls config drift through here in any shape and
// we want any byte change to roll the workload. Empty string when
// the ConfigMap doesn't exist in the namespace.
//
// Operational note: a single edit to authbridge-runtime-config
// re-hashes every AgentRuntime in the namespace and reconciles them
// in a burst. Kubernetes sequences the actual pod rolls per
// Deployment, but the controller's reconcile load scales linearly
// with the number of AgentRuntimes. For typical small namespaces
// (single-digit agents) this is fine; in larger deployments,
// formatting / whitespace edits to this CM during peak hours will
// trigger a noticeable rollout fan-out.
AuthBridgeRuntime string `json:"authBridgeRuntime,omitempty"`
}

type traceConfig struct {
Expand Down Expand Up @@ -111,9 +142,19 @@ func resolveConfig(ctx context.Context, c client.Reader, namespace string, spec
}
merged := mergeMaps(clusterDefaults, nsDefaults)

// Layer 2b: namespace authbridge-runtime-config (config.yaml).
// Captured raw so any byte change rolls the workload. The CM may
// not exist in every agent namespace; absence is normal and the
// admission webhook falls back to its own defaults.
abRuntime := ""
if data := readConfigMapData(ctx, c, namespace, AuthBridgeRuntimeConfigMapName); len(data) > 0 {
abRuntime = data["config.yaml"]
}

resolved := resolvedConfig{
FeatureGates: featureGates,
Defaults: merged,
FeatureGates: featureGates,
Defaults: merged,
AuthBridgeRuntime: abRuntime,
}

if spec == nil {
Expand Down Expand Up @@ -143,6 +184,9 @@ func resolveConfig(ctx context.Context, c client.Reader, namespace string, spec
}
}

resolved.AuthBridgeMode = spec.AuthBridgeMode
resolved.MTLSMode = spec.MTLSMode

return resolved, warnings
}

Expand Down
Loading
Loading