Skip to content

Commit 70978a1

Browse files
committed
feat: adding custom-http-filter flag. supporting buffer and wasm filter for the moment
1 parent 98af4b7 commit 70978a1

File tree

5 files changed

+152
-2
lines changed

5 files changed

+152
-2
lines changed

cmd/root.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ func init() {
113113
rootCmd.PersistentFlags().Bool("http-ext-authz-allow-partial-message", true, "When this field is true, Envoy will buffer the message until max_request_bytes is reached")
114114
rootCmd.PersistentFlags().Bool("http-ext-authz-pack-as-bytes", false, "When this field is true, Envoy will send the body as raw bytes.")
115115
rootCmd.PersistentFlags().Bool("http-ext-authz-failure-mode-allow", true, "Changes filters behaviour on errors")
116+
rootCmd.PersistentFlags().String("custom-http-filter-file", "", "Path to a custom HTTP filter file to load. The file should contain a valid Envoy HTTP filters list in JSON protobuff.")
116117

117118
rootCmd.PersistentFlags().Duration("default-route-timeout", 15*time.Second, "Default timeout of the routes")
118119
rootCmd.PersistentFlags().Duration("default-cluster-timeout", 30*time.Second, "Default timeout of the cluster")
@@ -155,6 +156,7 @@ func init() {
155156
viper.BindPFlag("defaultTimeouts.Cluster", rootCmd.PersistentFlags().Lookup("default-cluster-timeout"))
156157
viper.BindPFlag("defaultTimeouts.PerTry", rootCmd.PersistentFlags().Lookup("default-per-try-timeout"))
157158
viper.BindPFlag("alpnProtocols", rootCmd.PersistentFlags().Lookup("alpn-protocols"))
159+
viper.BindPFlag("customHttpFilterFile", rootCmd.PersistentFlags().Lookup("custom-http-filter-file"))
158160
}
159161

160162
func initConfig() {
@@ -261,6 +263,7 @@ func main(*cobra.Command, []string) error {
261263
envoy.WithAccessLog(c.AccessLogger),
262264
envoy.WithTracingProvider(viper.GetString("tracingProvider")),
263265
envoy.WithAlpnProtocols(viper.GetStringSlice("alpnProtocols")),
266+
envoy.WithCustomHttpFilterFile(viper.GetString("customHttpFilterFile")),
264267
)
265268
configurator.ValidateAndFormatPath()
266269
snapshotter := envoy.NewSnapshotter(envoyCache, configurator, aggregator)

pkg/envoy/boilerplate.go

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package envoy
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"log"
7+
"os"
68
"path/filepath"
79
"strings"
810

@@ -15,13 +17,16 @@ import (
1517
tracing "github.com/envoyproxy/go-control-plane/envoy/config/trace/v3"
1618
eal "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/file/v3"
1719
gal "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/grpc/v3"
20+
buffer "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/buffer/v3"
1821
eauthz "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_authz/v3"
1922
hcfg "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/health_check/v3"
23+
wasm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/wasm/v3"
2024
tls_inspector "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/tls_inspector/v3"
2125
hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
2226
previousHosts "github.com/envoyproxy/go-control-plane/envoy/extensions/retry/host/previous_hosts/v3"
2327
auth "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
2428
envoy_extension_http "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3"
29+
wasmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/wasm/v3"
2530
matcherv3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
2631
any "github.com/golang/protobuf/ptypes/any"
2732
"github.com/golang/protobuf/ptypes/duration"
@@ -125,6 +130,124 @@ func makeVirtualHost(vhost *virtualHost, reselectionAttempts int64, defaultRetry
125130
return &virtualHost, nil
126131
}
127132

133+
func loadCustomHttpFilters(filePath string) ([]*hcm.HttpFilter, error) {
134+
data, err := os.ReadFile(filePath)
135+
if err != nil {
136+
return nil, fmt.Errorf("failed to read custom HTTP filter file `%s`: %w", filePath, err)
137+
}
138+
var config CustomHttpFiltersConfig
139+
if err := json.Unmarshal(data, &config); err != nil {
140+
return nil, fmt.Errorf("failed to unmarshal custom HTTP filter file `%s`: %w", filePath, err)
141+
}
142+
var filters []*hcm.HttpFilter
143+
for _, filter := range config {
144+
var anyConfig *anypb.Any
145+
switch filter.Name {
146+
case "envoy.filters.http.wasm":
147+
wasmConfig, err := makeWasmConfig(filter.TypedConfig)
148+
if err != nil {
149+
return nil, fmt.Errorf("failed to create wasm config: %w", err)
150+
}
151+
anyConfig, err = anypb.New(wasmConfig)
152+
if err != nil {
153+
return nil, fmt.Errorf("failed to convert wasm config to Any: %w", err)
154+
}
155+
case "envoy.filters.http.buffer":
156+
bufferConfig, err := makeBufferConfig(filter.TypedConfig)
157+
if err != nil {
158+
return nil, fmt.Errorf("failed to create buffer config: %w", err)
159+
}
160+
anyConfig, err = anypb.New(bufferConfig)
161+
if err != nil {
162+
return nil, fmt.Errorf("failed to convert buffer config to Any: %w", err)
163+
}
164+
default:
165+
return nil, fmt.Errorf("unsupported filter type: %s", filter.Name)
166+
}
167+
filters = append(filters, &hcm.HttpFilter{
168+
Name: filter.Name,
169+
ConfigType: &hcm.HttpFilter_TypedConfig{
170+
TypedConfig: anyConfig,
171+
},
172+
})
173+
}
174+
return filters, nil
175+
}
176+
177+
func makeBufferConfig(typedConfig map[string]interface{}) (*buffer.Buffer, error) {
178+
maxRequestBytes, ok := typedConfig["maxRequestBytes"].(float64)
179+
if !ok {
180+
return nil, fmt.Errorf("missing or invalid `maxRequestBytes` field in buffer typedConfig")
181+
}
182+
183+
return &buffer.Buffer{
184+
MaxRequestBytes: wrapperspb.UInt32(uint32(maxRequestBytes)),
185+
}, nil
186+
}
187+
188+
func makeWasmConfig(typedConfig map[string]interface{}) (*wasm.Wasm, error) {
189+
value, ok := typedConfig["value"].(map[string]interface{})
190+
if !ok {
191+
return nil, fmt.Errorf("missing or invalid `value` field in wasm typedConfig")
192+
}
193+
config, ok := value["config"].(map[string]interface{})
194+
if !ok {
195+
return nil, fmt.Errorf("missing or invalid `config` field in wasm typedConfig")
196+
}
197+
vmConfig, ok := config["vm_config"].(map[string]interface{})
198+
if !ok {
199+
return nil, fmt.Errorf("missing or invalid `vm_config` field in wasm config")
200+
}
201+
runtime, ok := vmConfig["runtime"].(string)
202+
if !ok {
203+
return nil, fmt.Errorf("missing or invalid `runtime` field in wasm vm_config")
204+
}
205+
vmID, ok := vmConfig["vm_id"].(string)
206+
if !ok {
207+
return nil, fmt.Errorf("missing or invalid `vm_id` field in wasm vm_config")
208+
}
209+
code, ok := vmConfig["code"].(map[string]interface{})
210+
if !ok {
211+
return nil, fmt.Errorf("missing or invalid `code` field in wasm vm_config")
212+
}
213+
local, ok := code["local"].(map[string]interface{})
214+
if !ok {
215+
return nil, fmt.Errorf("missing or invalid `local` field in wasm code")
216+
}
217+
filename, ok := local["filename"].(string)
218+
if !ok {
219+
return nil, fmt.Errorf("missing or invalid `filename` field in wasm local code")
220+
}
221+
configuration, ok := config["configuration"].(map[string]interface{})
222+
if !ok {
223+
return nil, fmt.Errorf("missing or invalid `configuration` field in wasm config")
224+
}
225+
return &wasm.Wasm{
226+
Config: &wasmv3.PluginConfig{
227+
Name: config["name"].(string),
228+
RootId: config["root_id"].(string),
229+
Configuration: &anypb.Any{
230+
Value: []byte(configuration["value"].(string)),
231+
},
232+
Vm: &wasmv3.PluginConfig_VmConfig{
233+
VmConfig: &wasmv3.VmConfig{
234+
Runtime: runtime,
235+
VmId: vmID,
236+
Code: &core.AsyncDataSource{
237+
Specifier: &core.AsyncDataSource_Local{
238+
Local: &core.DataSource{
239+
Specifier: &core.DataSource_Filename{
240+
Filename: filename,
241+
},
242+
},
243+
},
244+
},
245+
},
246+
},
247+
},
248+
}, nil
249+
}
250+
128251
func makeHealthConfig() *hcfg.HealthCheck {
129252
return &hcfg.HealthCheck{
130253
PassThroughMode: &wrappers.BoolValue{Value: false},
@@ -247,11 +370,21 @@ func (c *KubernetesConfigurator) makeConnectionManager(virtualHosts []*route.Vir
247370
// HTTP Filters
248371
filterBuilder := &httpFilterBuilder{}
249372

373+
// custom HTTP filters
374+
if c.customHttpFilterFile != "" {
375+
customFilters, err := loadCustomHttpFilters(c.customHttpFilterFile)
376+
if err != nil {
377+
log.Fatalf("failed to load custom HTTP filters: %s", err)
378+
}
379+
for _, filter := range customFilters {
380+
filterBuilder.Add(filter)
381+
}
382+
}
383+
250384
anyHealthConfig, err := anypb.New(makeHealthConfig())
251385
if err != nil {
252386
log.Fatalf("failed to marshal healthcheck config struct to typed struct: %s", err)
253387
}
254-
255388
filterBuilder.Add(&hcm.HttpFilter{
256389
Name: "envoy.filters.http.health_check",
257390
ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: anyHealthConfig},
@@ -268,7 +401,6 @@ func (c *KubernetesConfigurator) makeConnectionManager(virtualHosts []*route.Vir
268401
ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: anyExtAuthzConfig},
269402
})
270403
}
271-
272404
filter, err := filterBuilder.Filters()
273405
if err != nil {
274406
return &hcm.HttpConnectionManager{}, err

pkg/envoy/configurator.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ type KubernetesConfigurator struct {
8080
defaultRetryOn string
8181
tracingProvider string
8282
alpnProtocols []string
83+
customHttpFilterFile string
8384

8485
previousConfig *envoyConfiguration
8586
listenerVersion string

pkg/envoy/http_filters.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ type httpFilterBuilder struct {
1212
filters []*hcm.HttpFilter
1313
}
1414

15+
type CustomHttpFilter struct {
16+
Name string `json:"name"`
17+
TypedConfig map[string]interface{} `json:"typed_config"`
18+
}
19+
20+
type CustomHttpFiltersConfig []CustomHttpFilter
21+
1522
func (b *httpFilterBuilder) Add(filter *hcm.HttpFilter) *httpFilterBuilder {
1623
b.filters = append(b.filters, filter)
1724
return b

pkg/envoy/options.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,10 @@ func WithAlpnProtocols(alpnProtocols []string) option {
106106
c.alpnProtocols = alpnProtocols
107107
}
108108
}
109+
110+
// WithCustomHttpFilterFile configures the custom HTTP filter file path
111+
func WithCustomHttpFilterFile(customHttpFilterFile string) option {
112+
return func(c *KubernetesConfigurator) {
113+
c.customHttpFilterFile = customHttpFilterFile
114+
}
115+
}

0 commit comments

Comments
 (0)