Skip to content

Commit

Permalink
[backport] gateway2: use safer merging to avoid assuming zero values …
Browse files Browse the repository at this point in the history
…as being unset (#10559)

Signed-off-by: Shashank Ram <[email protected]>
  • Loading branch information
shashankram authored Jan 8, 2025
1 parent f0be1ac commit 7b72f42
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 18 deletions.
17 changes: 17 additions & 0 deletions changelog/v1.18.4/merge-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
changelog:
- type: NON_USER_FACING
resolvesIssue: false
description: |
gateway2: use safer merging to avoid assuming zero values as being unset
The legacy Edge code uses ShallowMerge() which can undesirably overwrite
zero values mistaking them for unset values. RouteOptions merging in
GatewayV2 uses the same API, but this can result in undesirable effects
if the merging considers zero valued fields as being unset. To avoid
this, the options merging used by GatewayV2 relies on a safer merge
that only allows merging of values that can be set to Nil (pointers,
slices, maps, etc.) which works since all user-facing fields on the
RouteOptions are nil-able. Functionally, this is the same as before
due to all fields being nil-able, but is a bit clearer to readers.
Moreover, trying to merge a non-nil field will panic which can catch
potential misuse of the API.
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,7 @@ var _ = DescribeTable("mergeOptionsForRoute",
PrefixRewrite: &wrapperspb.StringValue{Value: "/dst"},
Timeout: durationpb.New(10 * time.Second),
},
glooutils.OptionsMergedFull,
glooutils.OptionsMergedPartial, // PrefixRewrite is not overwritten
),
Entry("override and augment dst options with annotation: specific fields",
&gwv1.HTTPRoute{
Expand Down
2 changes: 1 addition & 1 deletion projects/gloo/pkg/translator/ssl_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func MergeSslConfig(dst, src *ssl.SslConfig) *ssl.SslConfig {

for i := range dstValue.NumField() {
dstField, srcField := dstValue.Field(i), srcValue.Field(i)
utils.ShallowMerge(dstField, srcField, false)
utils.ShallowMerge(dstField, srcField)
}

return dst
Expand Down
48 changes: 32 additions & 16 deletions projects/gloo/pkg/utils/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,28 @@ const (
OptionsMergedFull
)

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

if dst.CanSet() && !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) {
if dst.CanSet() && !isEmptyValue(src) && isEmptyValue(dst) {
dst.Set(src)
return true
}

return false
}

// maySetValue sets dst to the value of src if:
// - src is set (has a non-nil value) and
// - dst is nil or overwrite is true
//
// It returns a boolean indicating whether src overwrote dst.
func maySetValue(dst, src reflect.Value, overwrite bool) bool {
if src.CanSet() && !src.IsNil() && dst.CanSet() && (overwrite || dst.IsNil()) {
dst.Set(src)
return true
}
Expand Down Expand Up @@ -84,7 +98,7 @@ func ShallowMergeRouteOptions(dst, src *v1.RouteOptions) (*v1.RouteOptions, bool
overwrote := false
for i := range dstValue.NumField() {
dstField, srcField := dstValue.Field(i), srcValue.Field(i)
if srcOverride := ShallowMerge(dstField, srcField, false); srcOverride {
if srcOverride := ShallowMerge(dstField, srcField); srcOverride {
overwrote = true
}
}
Expand All @@ -110,7 +124,7 @@ func ShallowMergeVirtualHostOptions(dst, src *v1.VirtualHostOptions) (*v1.Virtua
overwrote := false
for i := range dstValue.NumField() {
dstField, srcField := dstValue.Field(i), srcValue.Field(i)
if srcOverride := ShallowMerge(dstField, srcField, false); srcOverride {
if srcOverride := ShallowMerge(dstField, srcField); srcOverride {
overwrote = true
}
}
Expand Down Expand Up @@ -147,23 +161,25 @@ func MergeRouteOptionsWithOverrides(dst, src *v1.RouteOptions, allowedOverrides
var dstFieldsOverwritten int
for i := range dstValue.NumField() {
dstField, srcField := dstValue.Field(i), srcValue.Field(i)

// NOTE: important to pre-compute this because dstFieldsOverwritten must be
// incremented based on the original value of dstField and not the overwritten value
dstIsSet := dstField.CanSet() && !dstField.IsNil()
if dstIsSet {
dstFieldsSet++
}

// Allow overrides if the field in dst is unset as merging from src into dst by default
// allows src to augment dst by setting fields unset in dst, hence the override check only
// applies when the field in dst is set: !isEmptyValue(dstField).
if !isEmptyValue(dstField) && overwriteByDefault && !(allowedOverrides.Has(wildcardField) ||
// applies when the field in dst is set (dstIsSet=true).
if dstIsSet && overwriteByDefault && !(allowedOverrides.Has(wildcardField) ||
allowedOverrides.Has(strings.ToLower(dstValue.Type().Field(i).Name))) {
continue
}
// NOTE: important to pre-compute this for use in the conditional below
// because dstFieldsOverwritten needs to be incremented based on the original value of dstField
// and not the state of the field after the merge
dstOverridable := dstField.CanSet() && !isEmptyValue(dstField)
if dstOverridable {
dstFieldsSet++
}
if srcOverride := ShallowMerge(dstField, srcField, overwriteByDefault); srcOverride {

if srcOverride := maySetValue(dstField, srcField, overwriteByDefault); srcOverride {
srcFieldsUsed++
if dstOverridable {
if dstIsSet {
dstFieldsOverwritten++
}
}
Expand Down

0 comments on commit 7b72f42

Please sign in to comment.