Skip to content

Commit 7b72f42

Browse files
authored
[backport] gateway2: use safer merging to avoid assuming zero values as being unset (#10559)
Signed-off-by: Shashank Ram <[email protected]>
1 parent f0be1ac commit 7b72f42

File tree

4 files changed

+51
-18
lines changed

4 files changed

+51
-18
lines changed

changelog/v1.18.4/merge-check.yaml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
changelog:
2+
- type: NON_USER_FACING
3+
resolvesIssue: false
4+
description: |
5+
gateway2: use safer merging to avoid assuming zero values as being unset
6+
7+
The legacy Edge code uses ShallowMerge() which can undesirably overwrite
8+
zero values mistaking them for unset values. RouteOptions merging in
9+
GatewayV2 uses the same API, but this can result in undesirable effects
10+
if the merging considers zero valued fields as being unset. To avoid
11+
this, the options merging used by GatewayV2 relies on a safer merge
12+
that only allows merging of values that can be set to Nil (pointers,
13+
slices, maps, etc.) which works since all user-facing fields on the
14+
RouteOptions are nil-able. Functionally, this is the same as before
15+
due to all fields being nil-able, but is a bit clearer to readers.
16+
Moreover, trying to merge a non-nil field will panic which can catch
17+
potential misuse of the API.

projects/gateway2/translator/plugins/routeoptions/route_options_plugin_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,7 @@ var _ = DescribeTable("mergeOptionsForRoute",
890890
PrefixRewrite: &wrapperspb.StringValue{Value: "/dst"},
891891
Timeout: durationpb.New(10 * time.Second),
892892
},
893-
glooutils.OptionsMergedFull,
893+
glooutils.OptionsMergedPartial, // PrefixRewrite is not overwritten
894894
),
895895
Entry("override and augment dst options with annotation: specific fields",
896896
&gwv1.HTTPRoute{

projects/gloo/pkg/translator/ssl_configuration.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func MergeSslConfig(dst, src *ssl.SslConfig) *ssl.SslConfig {
7272

7373
for i := range dstValue.NumField() {
7474
dstField, srcField := dstValue.Field(i), srcValue.Field(i)
75-
utils.ShallowMerge(dstField, srcField, false)
75+
utils.ShallowMerge(dstField, srcField)
7676
}
7777

7878
return dst

projects/gloo/pkg/utils/merge.go

+32-16
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,28 @@ const (
2525
OptionsMergedFull
2626
)
2727

28-
// ShallowMerge sets dst to the value of src, if src is non-zero and dst is zero-valued or overwrite=true.
28+
// ShallowMerge sets dst to the value of src, if src is non-zero and dst is zero-valued
2929
// It returns a boolean indicating whether src overwrote dst.
30-
func ShallowMerge(dst, src reflect.Value, overwrite bool) bool {
30+
func ShallowMerge(dst, src reflect.Value) bool {
3131
if !src.IsValid() {
3232
return false
3333
}
3434

35-
if dst.CanSet() && !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) {
35+
if dst.CanSet() && !isEmptyValue(src) && isEmptyValue(dst) {
36+
dst.Set(src)
37+
return true
38+
}
39+
40+
return false
41+
}
42+
43+
// maySetValue sets dst to the value of src if:
44+
// - src is set (has a non-nil value) and
45+
// - dst is nil or overwrite is true
46+
//
47+
// It returns a boolean indicating whether src overwrote dst.
48+
func maySetValue(dst, src reflect.Value, overwrite bool) bool {
49+
if src.CanSet() && !src.IsNil() && dst.CanSet() && (overwrite || dst.IsNil()) {
3650
dst.Set(src)
3751
return true
3852
}
@@ -84,7 +98,7 @@ func ShallowMergeRouteOptions(dst, src *v1.RouteOptions) (*v1.RouteOptions, bool
8498
overwrote := false
8599
for i := range dstValue.NumField() {
86100
dstField, srcField := dstValue.Field(i), srcValue.Field(i)
87-
if srcOverride := ShallowMerge(dstField, srcField, false); srcOverride {
101+
if srcOverride := ShallowMerge(dstField, srcField); srcOverride {
88102
overwrote = true
89103
}
90104
}
@@ -110,7 +124,7 @@ func ShallowMergeVirtualHostOptions(dst, src *v1.VirtualHostOptions) (*v1.Virtua
110124
overwrote := false
111125
for i := range dstValue.NumField() {
112126
dstField, srcField := dstValue.Field(i), srcValue.Field(i)
113-
if srcOverride := ShallowMerge(dstField, srcField, false); srcOverride {
127+
if srcOverride := ShallowMerge(dstField, srcField); srcOverride {
114128
overwrote = true
115129
}
116130
}
@@ -147,23 +161,25 @@ func MergeRouteOptionsWithOverrides(dst, src *v1.RouteOptions, allowedOverrides
147161
var dstFieldsOverwritten int
148162
for i := range dstValue.NumField() {
149163
dstField, srcField := dstValue.Field(i), srcValue.Field(i)
164+
165+
// NOTE: important to pre-compute this because dstFieldsOverwritten must be
166+
// incremented based on the original value of dstField and not the overwritten value
167+
dstIsSet := dstField.CanSet() && !dstField.IsNil()
168+
if dstIsSet {
169+
dstFieldsSet++
170+
}
171+
150172
// Allow overrides if the field in dst is unset as merging from src into dst by default
151173
// allows src to augment dst by setting fields unset in dst, hence the override check only
152-
// applies when the field in dst is set: !isEmptyValue(dstField).
153-
if !isEmptyValue(dstField) && overwriteByDefault && !(allowedOverrides.Has(wildcardField) ||
174+
// applies when the field in dst is set (dstIsSet=true).
175+
if dstIsSet && overwriteByDefault && !(allowedOverrides.Has(wildcardField) ||
154176
allowedOverrides.Has(strings.ToLower(dstValue.Type().Field(i).Name))) {
155177
continue
156178
}
157-
// NOTE: important to pre-compute this for use in the conditional below
158-
// because dstFieldsOverwritten needs to be incremented based on the original value of dstField
159-
// and not the state of the field after the merge
160-
dstOverridable := dstField.CanSet() && !isEmptyValue(dstField)
161-
if dstOverridable {
162-
dstFieldsSet++
163-
}
164-
if srcOverride := ShallowMerge(dstField, srcField, overwriteByDefault); srcOverride {
179+
180+
if srcOverride := maySetValue(dstField, srcField, overwriteByDefault); srcOverride {
165181
srcFieldsUsed++
166-
if dstOverridable {
182+
if dstIsSet {
167183
dstFieldsOverwritten++
168184
}
169185
}

0 commit comments

Comments
 (0)