Skip to content
Open
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
21 changes: 18 additions & 3 deletions api/v1alpha1/ext_proc_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,50 @@ type ExtProcProvider struct {
FailOpen bool `json:"failOpen,omitempty"`

// ProcessingMode defines how the filter should interact with the request/response streams.
// Envoy: Supported
// Agentgateway: Not Supported (ignored)
// +optional
ProcessingMode *ProcessingMode `json:"processingMode,omitempty"`

// MessageTimeout is the timeout for each message sent to the external processing server.
// Envoy: Supported
// Agentgateway: Not Supported (ignored)
// +optional
// +kubebuilder:validation:XValidation:rule="matches(self, '^([0-9]{1,5}(h|m|s|ms)){1,4}$')",message="invalid timeout value"
// +kubebuilder:validation:XValidation:rule="duration(self) >= duration('1ms')",message="timeout must be at least 1ms."
MessageTimeout *metav1.Duration `json:"messageTimeout,omitempty"`

// MaxMessageTimeout specifies the upper bound of override_message_timeout that may be sent from the external processing server.
// The default value 0, which effectively disables the override_message_timeout API.
// Envoy: Supported
// Agentgateway: Not Supported (ignored)
// +optional
// +kubebuilder:validation:XValidation:rule="matches(self, '^([0-9]{1,5}(h|m|s|ms)){1,4}$')",message="invalid timeout value"
// +kubebuilder:validation:XValidation:rule="duration(self) >= duration('1ms')",message="timeout must be at least 1ms."
MaxMessageTimeout *metav1.Duration `json:"maxMessageTimeout,omitempty"`

// StatPrefix is an optional prefix to include when emitting stats from the extproc filter,
// enabling different instances of the filter to have unique stats.
// Envoy: Supported
// Agentgateway: Not Supported (ignored)
// +optional
// +kubebuilder:validation:MinLength=1
StatPrefix *string `json:"statPrefix,omitempty"`

// RouteCacheAction describes the route cache action to be taken when an
// external processor response is received in response to request headers.
// The default behavior is "FromResponse" which will only clear the route cache when
// an external processing response has the clear_route_cache field set.
// If not specified, the Envoy default value of "FromResponse" will be used, which only clears
// the route cache when an external processing response has the clear_route_cache field set.
// Envoy: Supported (default: FromResponse)
// Agentgateway: Not Supported (ignored)
// +optional
// +kubebuilder:validation:Enum=FromResponse;Clear;Retain
// +kubebuilder:default=FromResponse
RouteCacheAction ExtProcRouteCacheAction `json:"routeCacheAction,omitempty"`

// MetadataOptions allows configuring metadata namespaces to forwarded or received from the external
// processing server.
// Envoy: Supported
// Agentgateway: Not Supported (ignored)
// +optional
MetadataOptions *MetadataOptions `json:"metadataOptions,omitempty"`
}
Expand Down Expand Up @@ -77,11 +88,15 @@ type ExtProcPolicy struct {
ExtensionRef *NamespacedObjectReference `json:"extensionRef,omitempty"`

// ProcessingMode defines how the filter should interact with the request/response streams
// Envoy: Supported
// Agentgateway: Not Supported (ignored)
// +optional
ProcessingMode *ProcessingMode `json:"processingMode,omitempty"`

// Disable all external processing filters.
// Can be used to disable external processing policies applied at a higher level in the config hierarchy.
// Envoy: Supported
// Agentgateway: Not Supported (ignored)
// +optional
Disable *PolicyDisable `json:"disable,omitempty"`
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.25.3

require (
// Also update AgentgatewayDefaultTag in pkg/deployer/wellknown.go and test/deployer/testdata/*
github.com/agentgateway/agentgateway v0.10.3
github.com/agentgateway/agentgateway v0.10.4
github.com/avast/retry-go/v4 v4.3.3
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443
github.com/envoyproxy/go-control-plane v0.13.5-0.20251015221300-4138018a492b
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ
github.com/ProtonMail/gopenpgp/v2 v2.7.1/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/agentgateway/agentgateway v0.10.3 h1:z5HcuNuiz+NTZR87sU91xsjxvBD9bRP2xOhJwGbgW8Y=
github.com/agentgateway/agentgateway v0.10.3/go.mod h1:geHd31xH5d8OrCRuRyvnDxBeqSZ9+O9VhL8VJAfcA+E=
github.com/agentgateway/agentgateway v0.10.4 h1:9zROecngjHJcdWwqoow2N0Ngs+c+yCgAwEptdqYz6n8=
github.com/agentgateway/agentgateway v0.10.4/go.mod h1:geHd31xH5d8OrCRuRyvnDxBeqSZ9+O9VhL8VJAfcA+E=
github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
Expand Down
2 changes: 1 addition & 1 deletion hack/utils/oss_compliance/osa_provided.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Name|Version|License
---|---|---
[cel.dev/expr](https://cel.dev/expr)|v0.24.0|Apache License 2.0
[agentgateway/agentgateway](https://github.com/agentgateway/agentgateway)|v0.10.3|Apache License 2.0
[agentgateway/agentgateway](https://github.com/agentgateway/agentgateway)|v0.10.4|Apache License 2.0
[anthropics/anthropic-sdk-go](https://github.com/anthropics/anthropic-sdk-go)|v1.13.0|MIT License
[retry-go/v4](https://github.com/avast/retry-go)|v4.3.3|MIT License
[xds/go](https://github.com/cncf/xds)|v0.0.0-20250501225837-2ac532fd4443|Apache License 2.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,15 +439,19 @@ spec:
description: |-
MaxMessageTimeout specifies the upper bound of override_message_timeout that may be sent from the external processing server.
The default value 0, which effectively disables the override_message_timeout API.
Envoy: Supported
Agentgateway: Not Supported (ignored)
type: string
x-kubernetes-validations:
- message: invalid timeout value
rule: matches(self, '^([0-9]{1,5}(h|m|s|ms)){1,4}$')
- message: timeout must be at least 1ms.
rule: duration(self) >= duration('1ms')
messageTimeout:
description: MessageTimeout is the timeout for each message sent
to the external processing server.
description: |-
MessageTimeout is the timeout for each message sent to the external processing server.
Envoy: Supported
Agentgateway: Not Supported (ignored)
type: string
x-kubernetes-validations:
- message: invalid timeout value
Expand All @@ -458,6 +462,8 @@ spec:
description: |-
MetadataOptions allows configuring metadata namespaces to forwarded or received from the external
processing server.
Envoy: Supported
Agentgateway: Not Supported (ignored)
properties:
forwarding:
description: Forwarding defines the typed or untyped dynamic
Expand All @@ -477,8 +483,10 @@ spec:
type: object
type: object
processingMode:
description: ProcessingMode defines how the filter should interact
with the request/response streams.
description: |-
ProcessingMode defines how the filter should interact with the request/response streams.
Envoy: Supported
Agentgateway: Not Supported (ignored)
properties:
requestBodyMode:
default: NONE
Expand Down Expand Up @@ -540,12 +548,13 @@ spec:
type: string
type: object
routeCacheAction:
default: FromResponse
description: |-
RouteCacheAction describes the route cache action to be taken when an
external processor response is received in response to request headers.
The default behavior is "FromResponse" which will only clear the route cache when
an external processing response has the clear_route_cache field set.
If not specified, the Envoy default value of "FromResponse" will be used, which only clears
the route cache when an external processing response has the clear_route_cache field set.
Envoy: Supported (default: FromResponse)
Agentgateway: Not Supported (ignored)
enum:
- FromResponse
- Clear
Expand All @@ -555,6 +564,8 @@ spec:
description: |-
StatPrefix is an optional prefix to include when emitting stats from the extproc filter,
enabling different instances of the filter to have unique stats.
Envoy: Supported
Agentgateway: Not Supported (ignored)
minLength: 1
type: string
required:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,8 @@ spec:
description: |-
Disable all external processing filters.
Can be used to disable external processing policies applied at a higher level in the config hierarchy.
Envoy: Supported
Agentgateway: Not Supported (ignored)
type: object
extensionRef:
description: ExtensionRef references the GatewayExtension that
Expand All @@ -1055,8 +1057,10 @@ spec:
- name
type: object
processingMode:
description: ProcessingMode defines how the filter should interact
with the request/response streams
description: |-
ProcessingMode defines how the filter should interact with the request/response streams
Envoy: Supported
Agentgateway: Not Supported (ignored)
properties:
requestBodyMode:
default: NONE
Expand Down
120 changes: 120 additions & 0 deletions pkg/agentgateway/plugins/traffic_plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const (
globalRateLimitPolicySuffix = ":rl-global"
transformationPolicySuffix = ":transformation"
csrfPolicySuffix = ":csrf"
extprocPolicySuffix = ":ext-proc"
)

var logger = logging.New("agentgateway/plugins")
Expand Down Expand Up @@ -398,6 +399,16 @@ func translateTrafficPolicyToAgw(
agwPolicies = append(agwPolicies, csrfPolicies...)
}

// Process extproc policies if present
if trafficPolicy.Spec.ExtProc != nil {
extProcPolicies, err := processExtProcPolicy(ctx, gatewayExtensions, trafficPolicy, policyName, policyTarget)
if err != nil {
logger.Error("error processing extproc policy", "error", err)
errs = append(errs, err)
}
agwPolicies = append(agwPolicies, extProcPolicies...)
}

return agwPolicies, errors.Join(errs...)
}

Expand Down Expand Up @@ -864,6 +875,115 @@ func processRateLimitPolicy(ctx krt.HandlerContext, gatewayExtensions krt.Collec
return agwPolicies, errors.Join(errs...)
}

func processExtProcPolicy(ctx krt.HandlerContext, gatewayExtensions krt.Collection[*v1alpha1.GatewayExtension], trafficPolicy *v1alpha1.TrafficPolicy, policyName string, policyTarget *api.PolicyTarget) ([]AgwPolicy, error) {
var errs []error

// validate that unsupported ExtProcPolicy fields are not set
if err := validateExtProcPolicy(trafficPolicy.Spec.ExtProc); err != nil {
errs = append(errs, err)
}

gwExt, err := lookupGatewayExtension(ctx, gatewayExtensions, *trafficPolicy.Spec.ExtProc.ExtensionRef, trafficPolicy.Namespace, v1alpha1.GatewayExtensionTypeExtProc)
if err != nil {
return nil, err
}

extProc := (*gwExt).Spec.ExtProc
Copy link

Copilot AI Oct 19, 2025

Choose a reason for hiding this comment

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

Potential nil dereference: Spec.ExtProc is accessed without a nil check. Add a guard (e.g., if extProc == nil { return nil, fmt.Errorf("gateway extension %s/%s missing extProc spec", …) }) before accessing extProc.GrpcService.

Suggested change
extProc := (*gwExt).Spec.ExtProc
extProc := (*gwExt).Spec.ExtProc
if extProc == nil {
return nil, fmt.Errorf("gateway extension %s/%s missing extProc spec", trafficPolicy.Namespace, trafficPolicy.Name)
}

Copilot uses AI. Check for mistakes.
if extProc == nil {
return nil, fmt.Errorf("extproc provider is missing from gateway extension %s/%s", gwExt.Namespace, gwExt.Namespace)
}

// validate that unsupported ExtProcProvider fields are not set
if err := validateExtProcProvider(extProc); err != nil {
errs = append(errs, err)
}

var extProcSvcTarget *api.BackendReference
if extProc.GrpcService != nil && extProc.GrpcService.BackendRef != nil {
var err error
extProcSvcTarget, err = buildAGWServiceRef(extProc.GrpcService.BackendRef, trafficPolicy.Namespace)
if err != nil {
return nil, fmt.Errorf("failed to build extproc service reference: %w", err)
}
}

if extProcSvcTarget == nil {
return nil, fmt.Errorf("extproc policy %s/%s missing backendRef in gateway extension %s/%s",
trafficPolicy.Namespace, trafficPolicy.Name, (*gwExt).Namespace, (*gwExt).Name)
}

failureMode := api.PolicySpec_ExtProc_FAIL_CLOSED
if extProc.FailOpen {
failureMode = api.PolicySpec_ExtProc_FAIL_OPEN
}

extProcPolicy := &api.Policy{
Name: policyName + extprocPolicySuffix + attachmentName(policyTarget),
Target: policyTarget,
Spec: &api.PolicySpec{
Kind: &api.PolicySpec_ExtProc_{
ExtProc: &api.PolicySpec_ExtProc{
Target: extProcSvcTarget,
FailureMode: failureMode,
},
},
},
}

logger.Debug("generated ExtProc policy",
"policy", trafficPolicy.Name,
"agentgateway_policy", extProcPolicy.Name,
"target", extProcSvcTarget)

return []AgwPolicy{{Policy: extProcPolicy}}, errors.Join(errs...)
}

// validateExtProcPolicy validates that unsupported ExtProcPolicy fields are not set.
func validateExtProcPolicy(policy *v1alpha1.ExtProcPolicy) error {
var errs []error

if policy.ProcessingMode != nil {
errs = append(errs, fmt.Errorf("processingMode field is not supported for agentgateway"))
}

if policy.Disable != nil {
errs = append(errs, fmt.Errorf("disable field is not supported for agentgateway"))
}

return errors.Join(errs...)
}

// validateExtProcProvider validates that unsupported ExtProcProvider fields are not set.
func validateExtProcProvider(provider *v1alpha1.ExtProcProvider) error {
var errs []error

if provider.ProcessingMode != nil {
errs = append(errs, fmt.Errorf("processingMode field in ExtProcProvider is not supported for agentgateway"))
}

if provider.MessageTimeout != nil {
errs = append(errs, fmt.Errorf("messageTimeout field in ExtProcProvider is not supported for agentgateway"))
}

if provider.MaxMessageTimeout != nil {
errs = append(errs, fmt.Errorf("maxMessageTimeout field in ExtProcProvider is not supported for agentgateway"))
}

if provider.StatPrefix != nil {
errs = append(errs, fmt.Errorf("statPrefix field in ExtProcProvider is not supported for agentgateway"))
}

if provider.RouteCacheAction != "" {
errs = append(errs, fmt.Errorf("routeCacheAction field in ExtProcProvider is not supported for agentgateway"))
}

if provider.MetadataOptions != nil {
errs = append(errs, fmt.Errorf("metadataOptions field in ExtProcProvider is not supported for agentgateway"))
}

return errors.Join(errs...)
}

// processLocalRateLimitPolicy processes local rate limiting configuration
func processLocalRateLimitPolicy(trafficPolicy *v1alpha1.TrafficPolicy, policyName string, policyTarget *api.PolicyTarget) (*AgwPolicy, error) {
if trafficPolicy.Spec.RateLimit.Local.TokenBucket == nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/deployer/wellknown.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const (
AgentgatewayRegistry = "ghcr.io/agentgateway"
// AgentgatewayDefaultTag is the default agentgateway image tag
// Note: should be in sync with version in go.mod and test/deployer/testdata/*
AgentgatewayDefaultTag = "0.10.3"
AgentgatewayDefaultTag = "0.10.4"
// SdsImage is the image of the sds container.
SdsImage = "sds"
// SdsContainerName is the name of the container in the proxy deployment for the SDS integration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ spec:
value: "0"
containers:
- name: agent-gateway
image: "ghcr.io/agentgateway/agentgateway:0.10.3"
image: "ghcr.io/agentgateway/agentgateway:0.10.4"
securityContext:
allowPrivilegeEscalation: false
capabilities:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ spec:
serviceAccountName: gw
containers:
- name: agent-gateway
image: "ghcr.io/agentgateway/agentgateway:0.10.3"
image: "ghcr.io/agentgateway/agentgateway:0.10.4"
startupProbe:
failureThreshold: 60
httpGet:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ spec:
serviceAccountName: gw
containers:
- name: agent-gateway
image: "ghcr.io/agentgateway/agentgateway:0.10.3"
image: "ghcr.io/agentgateway/agentgateway:0.10.4"
startupProbe:
failureThreshold: 60
httpGet:
Expand Down
2 changes: 1 addition & 1 deletion test/deployer/testdata/agentgateway-out.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ spec:
value: "0"
containers:
- name: agent-gateway
image: "ghcr.io/agentgateway/agentgateway:0.10.3"
image: "ghcr.io/agentgateway/agentgateway:0.10.4"
securityContext:
allowPrivilegeEscalation: false
capabilities:
Expand Down
2 changes: 1 addition & 1 deletion test/deployer/testdata/agentgateway-tls-out.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ spec:
value: "0"
containers:
- name: agent-gateway
image: "ghcr.io/agentgateway/agentgateway:0.10.3"
image: "ghcr.io/agentgateway/agentgateway:0.10.4"
securityContext:
allowPrivilegeEscalation: false
capabilities:
Expand Down
10 changes: 10 additions & 0 deletions test/e2e/features/agentgateway/extproc/example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Build output
_output/
*.o
*.a

# Binary
server

# Go modules cache
go.sum.bak
Loading