-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathconfigparser.go
More file actions
268 lines (218 loc) · 10.3 KB
/
Copy pathconfigparser.go
File metadata and controls
268 lines (218 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
package gonvoy
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"github.com/ardikabs/gonvoy/pkg/util"
xds "github.com/cncf/xds/go/xds/type/v3"
"github.com/tidwall/gjson"
"github.com/envoyproxy/envoy/contrib/golang/common/go/api"
"google.golang.org/protobuf/types/known/anypb"
)
// ConfigOptions represents the configuration options for the filters.
type ConfigOptions struct {
// FilterConfig represents the filter configuration.
FilterConfig interface{}
// AlwaysUseChildConfig intend to disable merge behavior, ensuring that it always references the child filter configuration.
//
AlwaysUseChildConfig bool
// IgnoreMergeError specifies during a merge error, instead of panicking, it will fallback to the root configuration.
//
IgnoreMergeError bool
// MetricsPrefix specifies the prefix used for metrics.
MetricsPrefix string
// AutoReloadRoute specifies whether the route should be auto reloaded when the request headers changes.
// It recommends to set this to true when the filter is used in a route configuration and the route is expected to change dynamically within certain conditions.
AutoReloadRoute bool
// DisableStrictBodyAccess specifies whether HTTP body access follows strict rules.
// As its name goes, it defaults to strict, which mean that HTTP body access and manipulation is only possible
// with the presence of the `X-Content-Operation` header, with accepted values being `ReadOnly` and `ReadWrite`.
// Even when disabled, users must explicitly enable body access, either for a Request or Response, to carry out operations.
// See EnableRequestRead, EnableRequestWrite, EnableResponseRead, EnableRequestWrite variables.
//
DisableStrictBodyAccess bool
// EnableRequestBodyRead specifies whether an HTTP Request Body can be accessed.
// It defaults to false, meaning any operation on OnRequestBody will be ignored.
// When enabled, operations on OnRequestBody are allowed,
// but attempts to modify the HTTP Request body will result in a panic, which returns with 502 (Bad Gateway).
//
// Warning! Use this option only when necessary, as enabling it is equivalent to granting access
// to potentially sensitive information that shouldn't be visible to the middleware otherwise.
EnableRequestBodyRead bool
// EnableRequestBodyWrite specifies whether an HTTP Request Body can be modified.
// It defaults to false, meaning any operation on OnRequestBody will be ignored.
// When enabled, operations on OnRequestBody, including modifications to the HTTP Request body, are permitted.
//
// Warning! Use this option only when necessary, as enabling it is equivalent to granting access
// to potentially sensitive information that shouldn't be visible to the middleware otherwise.
EnableRequestBodyWrite bool
// EnableResponseBodyRead specifies whether an HTTP Response Body can be accessed or not.
// It defaults to false, meaning any operation on OnResponseBody will be ignored.
// When enabled, operations on OnResponseBody are allowed,
// but attempts to modify the HTTP Response body will result in a panic, which returns with 502 (Bad Gateway).
//
// Warning! Use this option only when necessary, as enabling it is equivalent to granting access
// to potentially sensitive information that shouldn't be visible to the middleware otherwise.
EnableResponseBodyRead bool
// EnableResponseBodyWrite specifies whether an HTTP Response Body can be accessed or not
// It defaults to false, meaning any operation on OnResponseBody will be ignored.
// When enabled, operations on OnResponseBody, including modifications to the HTTP Response body, are permitted.
//
// Warning! Use this option only when necessary, as enabling it is equivalent to granting access
// to potentially sensitive information that shouldn't be visible to the middleware otherwise.
EnableResponseBodyWrite bool
// DisableChunkedEncodingRequest specifies whether the request body should not be chunked during OnRequestBody phases.
// This setting applies when EnableRequestBodyWrite is enabled.
// It defaults to false, meaning if EnableRequestBodyWrite is enabled, the filter is expected to modify the request body, hence it will be chunked following the Content-Length header removal.
// However, this setting only relevant for protocols prior to HTTP/2, as it will be treated as having chunked encoding (Transfer-Encoding: chunked).
// Therefore turning this on will preserve the Content-Length header.
//
// Note that when this setting is turned on (re: disabled), the request headers will be buffered into the filter manager.
// Hence, you can modify request headers as well in the OnRequestBody phase.
DisableChunkedEncodingRequest bool
// DisableChunkedEncodingResponse specifies whether the response should not be chunked during OnResponseBody phases.
// This setting applies when EnableResponseBodyWrite is enabled.
// It defaults to false, meaning if EnableResponseBodyWrite is enabled,
// the filter is expected to modify the response body, hence it will be chunked following the Content-Length header removal.
// However, this setting only relevant for protocols prior to HTTP/2, as it will be treated as having chunked encoding (Transfer-Encoding: chunked).
// Therefore turning this on will preserve the Content-Length header.
//
DisableChunkedEncodingResponse bool
}
type configParser struct {
options ConfigOptions
rootGlobalConfig *internalConfig
}
func NewConfigParser(options ConfigOptions) api.StreamFilterConfigParser {
return &configParser{
options: options,
rootGlobalConfig: newInternalConfig(options),
}
}
func (p *configParser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) {
// Parse the filter configuration if it is provided
filterConfig, err := p.parseFilterConfig(any)
if err != nil {
return nil, err
}
// Handle the root (parent) plugin configuration
if callbacks != nil {
// Renew the config callbacks and filter config once root plugin configuration updated
p.rootGlobalConfig.callbacks = callbacks
p.rootGlobalConfig.filterConfig = filterConfig
return p.rootGlobalConfig, nil
}
// Create a copy of the root global config for the child filter config
// This shares all attributes except the filter config
copyGlobalConfig := *p.rootGlobalConfig
copyGlobalConfig.filterConfig = filterConfig
return ©GlobalConfig, nil
}
func (p *configParser) parseFilterConfig(any *anypb.Any) (filterCfg interface{}, err error) {
if any.GetValue() == nil {
return nil, nil
}
configStruct := &xds.TypedStruct{}
if err := any.UnmarshalTo(configStruct); err != nil {
return nil, fmt.Errorf("configparser: parse failed; %w", err)
}
v := configStruct.Value
b, err := v.MarshalJSON()
if err != nil {
return nil, fmt.Errorf("configparser: parse failed; %w", err)
}
if util.IsNil(p.options.FilterConfig) {
filterCfg = gjson.ParseBytes(b)
} else {
filterCfg, err = util.NewFrom(p.options.FilterConfig)
if err != nil {
return nil, fmt.Errorf("configparser: parse failed; %w", err)
}
if err := json.Unmarshal(b, &filterCfg); err != nil {
return nil, fmt.Errorf("configparser: parse failed; %w", err)
}
}
return filterCfg, nil
}
func (p *configParser) Merge(parent, child interface{}) interface{} {
origParentGlobalConfig, parentOK := parent.(*internalConfig)
origChildGlobalConfig, childOK := child.(*internalConfig)
if !parentOK && !childOK {
panic("configparser: merge failed; both parent and child configs uses unknown data types")
}
if util.IsNil(p.options.FilterConfig) {
origChildGlobalConfig.filterConfig = p.mergeLiteral(origParentGlobalConfig.filterConfig, origChildGlobalConfig.filterConfig)
return origChildGlobalConfig
}
mergedFilterCfg, err := p.mergeStruct(origParentGlobalConfig.filterConfig, origChildGlobalConfig.filterConfig)
if err != nil {
if p.options.IgnoreMergeError {
return origParentGlobalConfig
}
panic(err)
}
origChildGlobalConfig.filterConfig = mergedFilterCfg
return origChildGlobalConfig
}
func (p *configParser) mergeLiteral(parent, child interface{}) gjson.Result {
parentJSON, parentJSONValid := parent.(gjson.Result)
childJSON, childJSONValid := child.(gjson.Result)
if !parentJSONValid && !childJSONValid {
return gjson.Result{}
}
parentJSONMap, parentMapValid := parentJSON.Value().(map[string]interface{})
childJSONMap, childMapValid := childJSON.Value().(map[string]interface{})
if !parentMapValid && !childMapValid {
return gjson.Result{}
}
for k, v := range parentJSONMap {
if _, ok := childJSONMap[k]; !ok {
childJSONMap[k] = v
}
}
jsonBytes, err := json.Marshal(childJSONMap)
if err != nil {
return gjson.Result{}
}
return gjson.ParseBytes(jsonBytes)
}
func (p *configParser) mergeStruct(parent, child interface{}) (interface{}, error) {
origParentPtr := reflect.ValueOf(parent)
origParentValue := origParentPtr.Elem()
parentType := origParentPtr.Type().Elem()
parentPtr := reflect.New(parentType) // *FilterConfigDataType
childPtr := reflect.ValueOf(child) // *FilterConfigDataType
if parentPtr.Kind() != reflect.Pointer || childPtr.Kind() != reflect.Pointer {
return nil, fmt.Errorf("configparser: merge failed; both parent(%s) and child(%s) configs MUST be pointers", parentPtr.Type(), childPtr.Type())
}
parentValue := parentPtr.Elem() // FilterConfigDataType
childValue := childPtr.Elem() // FilterConfigDataType
switch {
case parentValue.Type() != childValue.Type():
return nil, fmt.Errorf("configparser: merge failed; parent(%s) and child(%s) configs have different data types", parentValue.Type(), childValue.Type())
case parentValue.Kind() != reflect.Struct || childValue.Kind() != reflect.Struct:
return nil, fmt.Errorf("configparser: merge failed; both parent(%s) and child(%s) configs MUST be references to a struct", parentValue.Kind(), childValue.Kind())
}
if p.options.AlwaysUseChildConfig {
return child, nil
}
parentValue.Set(origParentValue)
for i := 0; i < childValue.NumField(); i++ {
tags, ok := parentType.Field(i).Tag.Lookup("envoy")
if !ok {
continue
}
v := childValue.Field(i)
isValidField := v.IsValid() || v.CanSet()
isMergeable := strings.Contains(tags, "mergeable")
isPreserveable := strings.Contains(tags, "preserve_root") && v.IsZero()
if !isValidField ||
!isMergeable ||
isPreserveable {
continue
}
parentValue.Field(i).Set(v)
}
return parentPtr.Interface(), nil
}