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
78 changes: 20 additions & 58 deletions charts/kagenti-operator/crds/agent.kagenti.dev_agentcards.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,51 +81,22 @@ spec:
identityBinding:
description: IdentityBinding specifies SPIFFE identity binding configuration
properties:
allowedSpiffeIDs:
description: |-
AllowedSpiffeIDs is the allowlist of SPIFFE IDs that can bind to this agent.
Each ID must be a valid SPIFFE ID in the format spiffe://<trust-domain>/<path>.
The SPIFFE ID from the JWS protected header must match one of these entries.
items:
description: SpiffeID represents a SPIFFE identity in the format
spiffe://<trust-domain>/<path>
pattern: ^spiffe://[a-zA-Z0-9][a-zA-Z0-9\-\.]*[a-zA-Z0-9](/[a-zA-Z0-9\-\._~%!$&'()*+,;=:@]+)*$
type: string
minItems: 1
type: array
expectedSpiffeID:
description: |-
Deprecated: ExpectedSpiffeID is no longer used. The SPIFFE ID now comes exclusively
from the JWS protected header (sign with --spiffe-id). This ensures all identity
claims are cryptographically bound to the signature.
This field is retained for backward compatibility and will be removed in a future release.
pattern: ^spiffe://[a-zA-Z0-9][a-zA-Z0-9\-\.]*[a-zA-Z0-9](/[a-zA-Z0-9\-\._~%!$&'()*+,;=:@]+)*$
type: string
strict:
default: false
description: |-
Strict enables strict enforcement mode for identity binding.
When true, binding failures result in network isolation: the signature-verified
label is removed from pods, and NetworkPolicy restricts all ingress/egress.
When false (audit mode), binding results are recorded in status but network
access is not affected.
NOTE: Scale-to-zero enforcement is only available via the legacy Agent CRD controller.
Strict enables enforcement mode: binding failures trigger network isolation.
When false (default), results are recorded in status only (audit mode).
type: boolean
trustDomain:
description: |-
Deprecated: TrustDomain is no longer used. The trust domain is determined
from the SPIFFE ID in the JWS protected header.
This field is retained for backward compatibility and will be removed in a future release.
TrustDomain overrides the operator-level --spire-trust-domain for this AgentCard.
If empty, the operator flag value is used.
pattern: ^[a-zA-Z0-9]([a-zA-Z0-9\-\.]*[a-zA-Z0-9])?$
type: string
required:
- allowedSpiffeIDs
type: object
selector:
description: |-
Selector identifies the Agent to index using label matching.
Deprecated: Use TargetRef instead. Selector is kept for backward compatibility.
If both TargetRef and Selector are specified, TargetRef takes precedence.
description: 'Deprecated: Use TargetRef instead. If both are set,
TargetRef takes precedence.'
properties:
matchLabels:
additionalProperties:
Expand All @@ -143,9 +114,8 @@ spec:
type: string
targetRef:
description: |-
TargetRef identifies the workload backing this agent using duck typing.
The referenced workload must have the required Kagenti labels (kagenti.io/type=agent).
This is the preferred way to reference agent workloads.
TargetRef identifies the workload backing this agent (duck typing).
The workload must have the kagenti.io/type=agent label.
properties:
apiVersion:
description: APIVersion is the API version of the target resource
Expand Down Expand Up @@ -175,7 +145,7 @@ spec:
evaluation
properties:
bound:
description: Bound indicates whether the expected SPIFFE ID is
description: Bound indicates whether the verified SPIFFE ID is
in the allowlist
type: boolean
lastEvaluationTime:
Expand Down Expand Up @@ -228,14 +198,10 @@ spec:
description: Name is the human-readable name of the agent
type: string
signatures:
description: |-
Signatures contains JWS signatures per A2A spec section 8.4.2.
Each element uses JWS JSON Serialization with protected header containing
the algorithm (alg), key ID (kid), and optional SPIFFE ID (spiffe_id).
description: Signatures contains JWS signatures per A2A spec §8.4.2.
items:
description: |-
AgentCardSignature represents a JWS signature on an AgentCard.
Follows the A2A specification section 8.4.2 — JWS JSON Serialization.
description: AgentCardSignature represents a JWS signature on
an AgentCard (A2A spec §8.4.2).
properties:
header:
description: Header contains optional unprotected JWS header
Expand All @@ -247,14 +213,12 @@ spec:
type: string
type: object
protected:
description: |-
Protected is the base64url-encoded JWS protected header.
Decoded, it contains {"alg":"RS256","kid":"key-1","spiffe_id":"spiffe://..."}.
description: Protected is the base64url-encoded JWS protected
header (contains alg, kid, spiffe_id).
type: string
signature:
description: |-
Signature is the base64url-encoded JWS signature value.
The signing input is: BASE64URL(protected) || '.' || BASE64URL(canonical_payload)
description: Signature is the base64url-encoded JWS signature
value.
type: string
required:
- protected
Expand Down Expand Up @@ -401,18 +365,16 @@ spec:
description: Protocol is the detected agent protocol (e.g., "a2a")
type: string
signatureIdentityMatch:
description: |-
SignatureIdentityMatch indicates if both signature AND identity binding pass.
true only when ValidSignature is true AND BindingStatus.Bound is true.
description: SignatureIdentityMatch is true when both signature and
identity binding pass.
type: boolean
signatureKeyId:
description: SignatureKeyID is the key ID used for verification (from
JWS protected header kid)
type: string
signatureSpiffeId:
description: |-
SignatureSpiffeID is the SPIFFE ID extracted from the JWS protected header.
This enables cross-referencing the signer's identity with the identity binding evaluation.
description: SignatureSpiffeID is the SPIFFE ID from the JWS protected
header (set only when valid).
type: string
signatureVerificationDetails:
description: SignatureVerificationDetails contains details about the
Expand Down
27 changes: 16 additions & 11 deletions charts/kagenti-operator/templates/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,29 @@ spec:
{{- end }}
{{- if .Values.signatureVerification.enabled }}
- "--require-a2a-signature=true"
- "--signature-provider={{ .Values.signatureVerification.provider }}"
{{- if .Values.signatureVerification.auditMode }}
- "--signature-audit-mode=true"
{{- end }}
{{- if .Values.signatureVerification.secret.name }}
- "--signature-secret-name={{ .Values.signatureVerification.secret.name }}"
{{- if .Values.signatureVerification.enforceNetworkPolicies }}
- "--enforce-network-policies=true"
{{- end }}
{{- if .Values.signatureVerification.secret.namespace }}
- "--signature-secret-namespace={{ .Values.signatureVerification.secret.namespace }}"
{{- if .Values.signatureVerification.spireTrustDomain }}
- "--spire-trust-domain={{ .Values.signatureVerification.spireTrustDomain }}"
{{- end }}
{{- if .Values.signatureVerification.secret.key }}
- "--signature-secret-key={{ .Values.signatureVerification.secret.key }}"
{{- if .Values.signatureVerification.spireTrustBundle.configMapName }}
- "--spire-trust-bundle-configmap={{ .Values.signatureVerification.spireTrustBundle.configMapName }}"
{{- end }}
{{- if .Values.signatureVerification.jwks.url }}
- "--signature-jwks-url={{ .Values.signatureVerification.jwks.url }}"
{{- if .Values.signatureVerification.spireTrustBundle.configMapNamespace }}
- "--spire-trust-bundle-configmap-namespace={{ .Values.signatureVerification.spireTrustBundle.configMapNamespace }}"
{{- end }}
{{- if .Values.signatureVerification.enforceNetworkPolicies }}
- "--enforce-network-policies=true"
{{- if .Values.signatureVerification.spireTrustBundle.configMapKey }}
- "--spire-trust-bundle-configmap-key={{ .Values.signatureVerification.spireTrustBundle.configMapKey }}"
{{- end }}
{{- if .Values.signatureVerification.spireTrustBundle.refreshInterval }}
- "--spire-trust-bundle-refresh-interval={{ .Values.signatureVerification.spireTrustBundle.refreshInterval }}"
{{- end }}
{{- if .Values.signatureVerification.svidExpiryGracePeriod }}
- "--svid-expiry-grace-period={{ .Values.signatureVerification.svidExpiryGracePeriod }}"
{{- end }}
{{- end }}
command:
Expand Down
22 changes: 11 additions & 11 deletions charts/kagenti-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,21 +84,21 @@ certmanager:
networkPolicy:
enable: false

# [SIGNATURE VERIFICATION]: A2A agent card signature verification
# [SIGNATURE VERIFICATION]: A2A agent card signature verification via SPIRE x5c
signatureVerification:
# Enable signature verification for agent cards
enabled: false
# Audit mode: log failures but don't block (use for gradual rollout)
auditMode: false
# Provider type: "secret", "jwks", or "none"
provider: "none"
# Secret provider configuration
secret:
name: ""
namespace: ""
key: ""
# JWKS provider configuration
jwks:
url: ""
# Enforce network policies based on signature verification
enforceNetworkPolicies: false
# SPIRE trust domain (required when enabled)
spireTrustDomain: ""
# SPIRE trust bundle ConfigMap (SPIFFE JSON format from BundlePublisher)
spireTrustBundle:
configMapName: ""
configMapNamespace: ""
configMapKey: "bundle.spiffe"
refreshInterval: "5m"
# How far before SVID expiry to trigger proactive workload restart
svidExpiryGracePeriod: "30m"
2 changes: 1 addition & 1 deletion kagenti-operator/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build the manager binary
FROM docker.io/golang:1.23 AS builder
FROM docker.io/golang:1.24 AS builder
ARG TARGETOS
ARG TARGETARCH

Expand Down
4 changes: 2 additions & 2 deletions kagenti-operator/GETTING_STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,8 +412,8 @@ kubectl run curl-test --image=curlimages/curl:8.1.2 --rm -i --tty -n team1 -- \
## Next Steps

- [Dynamic Agent Discovery](docs/dynamic-agent-discovery.md) — How AgentCard enables agent discovery
- [Signature Verification](docs/a2a-signature-verification.md) — Set up JWS signature verification
- [Identity Binding](docs/identity-binding-quickstart.md) — Configure SPIFFE identity binding
- [Signature Verification](docs/agentcard-signature-verification.md) — Set up JWS signature verification
- [Identity Binding](docs/agentcard-identity-binding.md) — Configure SPIFFE identity binding
- [Migration Guide](../docs/migration/migrate-agent-crd-to-workloads.md) — Migrating from Agent CRD to workloads
- [API Reference](docs/api-reference.md) — Full CRD specifications

Expand Down
11 changes: 11 additions & 0 deletions kagenti-operator/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,17 @@ build-installer: manifests generate kustomize ## Generate a consolidated YAML wi
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
$(KUSTOMIZE) build config/default > dist/install.yaml

SIGNER_IMG ?= kagenti/agentcard-signer:latest
KIND_CLUSTER_NAME ?= $(CLUSTER)

.PHONY: build-signer
build-signer: ## Build the agentcard-signer init-container image.
$(CONTAINER_TOOL) build -t $(SIGNER_IMG) -f cmd/agentcard-signer/Dockerfile .

.PHONY: load-signer-image
load-signer-image: ## Load the agentcard-signer image into Kind.
kind load docker-image $(SIGNER_IMG) --name $(KIND_CLUSTER_NAME)

##@ Deployment

ifndef ignore-not-found
Expand Down
13 changes: 0 additions & 13 deletions kagenti-operator/PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,4 @@ resources:
kind: Agent
path: github.com/kagenti/operator/api/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: kagenti.dev
group: agent
kind: AgentBuild
path: github.com/kagenti/operator/api/v1alpha1
version: v1alpha1
webhooks:
defaulting: true
validation: true
webhookVersion: v1
version: "3"
33 changes: 9 additions & 24 deletions kagenti-operator/api/v1alpha1/agentcard_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,29 +37,16 @@ type AgentCardSpec struct {
IdentityBinding *IdentityBinding `json:"identityBinding,omitempty"`
}

// SpiffeID represents a SPIFFE identity in the format spiffe://<trust-domain>/<path>
// +kubebuilder:validation:Pattern=`^spiffe://[a-zA-Z0-9][a-zA-Z0-9\-\.]*[a-zA-Z0-9](/[a-zA-Z0-9\-\._~%!$&'()*+,;=:@]+)*$`
type SpiffeID string

// IdentityBinding configures workload identity binding for an AgentCard.
// The SPIFFE ID used for binding comes from the JWS protected header (sign
// with --spiffe-id). If the header lacks a spiffe_id, binding fails.
// The SPIFFE ID is extracted from the leaf certificate SAN URI in the x5c chain.
// Binding validates that the SPIFFE ID belongs to the configured trust domain.
type IdentityBinding struct {
// Deprecated: No longer used; trust domain comes from the JWS protected header.
// TrustDomain overrides the operator-level --spire-trust-domain for this AgentCard.
// If empty, the operator flag value is used.
// +optional
// +kubebuilder:validation:Pattern=`^[a-zA-Z0-9]([a-zA-Z0-9\-\.]*[a-zA-Z0-9])?$`
TrustDomain string `json:"trustDomain,omitempty"`

// Deprecated: No longer used; SPIFFE ID comes from the JWS protected header.
// +optional
ExpectedSpiffeID SpiffeID `json:"expectedSpiffeID,omitempty"`

// AllowedSpiffeIDs is the allowlist of SPIFFE IDs permitted to bind to this agent.
// The SPIFFE ID from the JWS protected header must match one of these entries.
// +required
// +kubebuilder:validation:MinItems=1
AllowedSpiffeIDs []SpiffeID `json:"allowedSpiffeIDs"`

// Strict enables enforcement mode: binding failures trigger network isolation.
// When false (default), results are recorded in status only (audit mode).
// +optional
Expand All @@ -77,7 +64,6 @@ type TargetRef struct {
// +kubebuilder:validation:MinLength=1
Kind string `json:"kind"`

// Name is the name of the target resource
// +kubebuilder:validation:MinLength=1
Name string `json:"name"`
}
Expand Down Expand Up @@ -117,19 +103,19 @@ type AgentCardStatus struct {
// +optional
SignatureKeyID string `json:"signatureKeyId,omitempty"`

// SignatureSpiffeID is the SPIFFE ID from the JWS protected header (set only when valid).
// SignatureSpiffeID is the SPIFFE ID from the leaf certificate SAN URI (set only when valid).
// +optional
SignatureSpiffeID string `json:"signatureSpiffeId,omitempty"`

// SignatureIdentityMatch is true when both signature and identity binding pass.
// +optional
SignatureIdentityMatch *bool `json:"signatureIdentityMatch,omitempty"`

// CardId is the SHA256 hash of the JCS-canonicalized card content (optional drift detection)
// CardId is the SHA-256 hash of the card content for drift detection.
// +optional
CardId string `json:"cardId,omitempty"`

// ExpectedSpiffeID is the SPIFFE ID used for binding evaluation (from JWS protected header)
// ExpectedSpiffeID is the SPIFFE ID used for binding evaluation.
// +optional
ExpectedSpiffeID string `json:"expectedSpiffeID,omitempty"`

Expand All @@ -140,7 +126,7 @@ type AgentCardStatus struct {

// BindingStatus represents the result of identity binding evaluation
type BindingStatus struct {
// Bound indicates whether the verified SPIFFE ID is in the allowlist
// Bound indicates whether the verified SPIFFE ID belongs to the configured trust domain
Bound bool `json:"bound"`

// Reason is a machine-readable reason for the binding status
Expand Down Expand Up @@ -202,7 +188,7 @@ type AgentCardData struct {

// AgentCardSignature represents a JWS signature on an AgentCard (A2A spec §8.4.2).
type AgentCardSignature struct {
// Protected is the base64url-encoded JWS protected header (contains alg, kid, spiffe_id).
// Protected is the base64url-encoded JWS protected header (contains alg, kid, x5c).
// +required
Protected string `json:"protected"`

Expand Down Expand Up @@ -258,7 +244,6 @@ type AgentSkill struct {

// SkillParameter defines a parameter that a skill accepts
type SkillParameter struct {
// Name is the parameter name
// +optional
Name string `json:"name,omitempty"`

Expand Down
7 changes: 1 addition & 6 deletions kagenti-operator/api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading