Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions internal/envconfig/xds.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ var (
// the client side. For more details, see:
// https://github.com/grpc/proposal/blob/master/A93-xds-ext-proc.md
XDSClientExtProcEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_EXT_PROC_ON_CLIENT", false)

// GCPAuthenticationFilterEnabled enables the xDS GCP Authentication
// filter. For more details, see:
// https://github.com/grpc/proposal/blob/master/A83-xds-gcp-authn-filter.md
Expand Down
34 changes: 34 additions & 0 deletions internal/xds/httpfilter/extproc/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
*
* Copyright 2026 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package extproc

import (
pb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_proc/v3"
Comment thread
easwars marked this conversation as resolved.
Outdated
"google.golang.org/grpc/internal/xds/httpfilter"
)

type baseConfig struct {
httpfilter.FilterConfig
Comment thread
easwars marked this conversation as resolved.
config *pb.ExternalProcessor
}

type overrideConfig struct {
httpfilter.FilterConfig
config *pb.ExtProcOverrides
}
120 changes: 120 additions & 0 deletions internal/xds/httpfilter/extproc/ext_proc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
*
* Copyright 2026 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

// Package extproc implements the Envoy external processing filter.
package extproc

import (
"fmt"

"google.golang.org/grpc/internal/envconfig"
"google.golang.org/grpc/internal/xds/httpfilter"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"

fpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_proc/v3"
Comment thread
easwars marked this conversation as resolved.
Outdated
)

func init() {
if envconfig.XDSClientExtProcEnabled {
httpfilter.Register(builder{})
}
}

type builder struct{}

func (builder) TypeURLs() []string {
return []string{"type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor"}
Comment thread
easwars marked this conversation as resolved.
Outdated
}

// validateBodyProcessingMode ensures that the body processing mode is either
// NONE or GRPC.
func validateBodyProcessingMode(pMode *fpb.ProcessingMode) error {
Comment thread
easwars marked this conversation as resolved.
Outdated
if reqMode := pMode.GetRequestBodyMode(); reqMode != fpb.ProcessingMode_NONE && reqMode != fpb.ProcessingMode_GRPC {
Comment thread
easwars marked this conversation as resolved.
Outdated
return fmt.Errorf("ext_proc: invalid request body mode %v: want 'NONE' or 'GRPC'", reqMode)
Comment thread
easwars marked this conversation as resolved.
Outdated
}
if resMode := pMode.GetResponseBodyMode(); resMode != fpb.ProcessingMode_NONE && resMode != fpb.ProcessingMode_GRPC {
return fmt.Errorf("ext_proc: invalid response body mode %v: want 'NONE' or 'GRPC'", resMode)
}
Comment thread
eshitachandwani marked this conversation as resolved.
Outdated
return nil
}

func (builder) ParseFilterConfig(cfg proto.Message) (httpfilter.FilterConfig, error) {
if cfg == nil {
return nil, fmt.Errorf("ext_proc: nil base configuration message provided")
}
m, ok := cfg.(*anypb.Any)
if !ok {
return nil, fmt.Errorf("ext_proc: error parsing config %v: unknown type %T", cfg, cfg)
Comment thread
easwars marked this conversation as resolved.
Outdated
}
Comment thread
eshitachandwani marked this conversation as resolved.
msg := new(fpb.ExternalProcessor)
if err := m.UnmarshalTo(msg); err != nil {
return nil, fmt.Errorf("ext_proc: failed to unmarshal config %v: %v", cfg, err)
}
if msg.GetGrpcService() == nil {
return nil, fmt.Errorf("ext_proc: empty grpc_service provided in config %v", cfg)
}
if msg.GetGrpcService().GetGoogleGrpc() == nil {
Comment thread
easwars marked this conversation as resolved.
Outdated
return nil, fmt.Errorf("ext_proc: only google_grpc grpc_service is supported, got %v in config %v", msg.GrpcService.GetTargetSpecifier(), cfg)
}
if msg.GetProcessingMode() == nil {
return nil, fmt.Errorf("ext_proc: missing processing_mode in config %v", cfg)
}
if err := validateBodyProcessingMode(msg.GetProcessingMode()); err != nil {
return nil, err
}
if mr := msg.GetMutationRules(); mr != nil {
if err := mr.GetAllowExpression().Validate(); err != nil {
Comment thread
easwars marked this conversation as resolved.
Outdated
return nil, fmt.Errorf("ext_proc: %v", err)
Comment thread
easwars marked this conversation as resolved.
Outdated
}
if err := mr.GetDisallowExpression().Validate(); err != nil {
return nil, fmt.Errorf("ext_proc: %v", err)
}
}
Comment thread
easwars marked this conversation as resolved.
return baseConfig{config: msg}, nil
Comment thread
easwars marked this conversation as resolved.
Outdated
}

func (builder) ParseFilterConfigOverride(ov proto.Message) (httpfilter.FilterConfig, error) {
if ov == nil {
return nil, fmt.Errorf("ext_proc: nil override configuration provided")
}
m, ok := ov.(*anypb.Any)
if !ok {
return nil, fmt.Errorf("ext_proc: error parsing override %v: unknown type %T", ov, ov)
}
msg := new(fpb.ExtProcPerRoute)
if err := m.UnmarshalTo(msg); err != nil {
return nil, fmt.Errorf("ext_proc: failed to unmarshal override %v: %v", ov, err)
}
override := msg.GetOverrides()
// GrpcService can be optionally provided in the override config. If
// provided, it must be of type google_grpc.
if override.GetGrpcService() != nil && override.GrpcService.GetGoogleGrpc() == nil {
return nil, fmt.Errorf("ext_proc: only google_grpc grpc_service is supported, got %v in override %v", override.GrpcService.GetTargetSpecifier(), override)
}
if pm := override.GetProcessingMode(); pm != nil {
Comment thread
eshitachandwani marked this conversation as resolved.
if err := validateBodyProcessingMode(pm); err != nil {
return nil, err
}
}
return overrideConfig{config: override}, nil
}

func (builder) IsTerminal() bool {
return false
}
Loading
Loading