Repository: kumahq/kuma
Pull request #5726 (2023-01-17 10:04:33)

chore(MeshHTTPRoute): add query params match support
### Checklist prior to review

<!--
Each of these sections need to be filled by the author when opening the PR.

If something doesn't apply please check the box and add a justification after the `--`
-->

- [x] Link to docs PR or issue --
- [x] Link to UI issue or PR --
- [x] Is the [issue worked on linked][1]? -- https://github.com/kumahq/kuma/issues/5470
- [x] The PR does not hardcode values that might break projects that depend on kuma (e.g. "kumahq" as a image registry) --
- [x] The PR will work for both Linux and Windows, system specific functions like `syscall.Mkfifo` have equivalent implementation on the other OS --
- [x] Unit Tests --
- [x] E2E Tests -- none
- [x] Manual Universal Tests --
- [x] Manual Kubernetes Tests -- none
- [x] Do you need to update [`UPGRADE.md`](../blob/master/UPGRADE.md)? --
- [x] Does it need to be backported according to the [backporting policy](../blob/master/CONTRIBUTING.md#backporting)? --
- [x] Do you need to explicitly set a [`> Changelog:` entry here](../blob/master/CONTRIBUTING.md#submitting-a-patch) or add a `ci/` label to run fewer/more tests?

[1]: https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword



Patch:
diff --git a/deployments/charts/kuma/crds/kuma.io_meshhttproutes.yaml b/deployments/charts/kuma/crds/kuma.io_meshhttproutes.yaml
index ca51a0baf..6ebbd7b21 100644
--- a/deployments/charts/kuma/crds/kuma.io_meshhttproutes.yaml
+++ b/deployments/charts/kuma/crds/kuma.io_meshhttproutes.yaml
@@ -304,6 +304,28 @@ spec:
                                   - type
                                   - value
                                   type: object
+                                queryParams:
+                                  description: QueryParams matches based on HTTP URL
+                                    query parameters. Multiple matches are ANDed together
+                                    such that all listed matches must succeed.
+                                  items:
+                                    properties:
+                                      name:
+                                        minLength: 1
+                                        type: string
+                                      type:
+                                        enum:
+                                        - Exact
+                                        - RegularExpression
+                                        type: string
+                                      value:
+                                        type: string
+                                    required:
+                                    - name
+                                    - type
+                                    - value
+                                    type: object
+                                  type: array
                               type: object
                             type: array
                         required:
diff --git a/deployments/charts/kuma/crds/kuma.io_meshtraces.yaml b/deployments/charts/kuma/crds/kuma.io_meshtraces.yaml
index e0daaa2be..18104742b 100644
--- a/deployments/charts/kuma/crds/kuma.io_meshtraces.yaml
+++ b/deployments/charts/kuma/crds/kuma.io_meshtraces.yaml
@@ -64,13 +64,19 @@ spec:
                               description: Address of Datadog collector, only host
                                 and port are allowed (no paths, fragments etc.)
                               type: string
+                          required:
+                          - url
                           type: object
                         zipkin:
                           description: Zipkin backend configuration.
                           properties:
                             apiVersion:
+                              default: httpJson
                               description: 'Version of the API. values: httpJson,
                                 httpProto. Default: httpJson see https://github.com/envoyproxy/envoy/blob/v1.22.0/api/envoy/config/trace/v3/zipkin.proto#L66'
+                              enum:
+                              - httpJson
+                              - httpProto
                               type: string
                             sharedSpanContext:
                               description: 'Determines whether client and server spans
@@ -82,9 +88,10 @@ spec:
                             url:
                               description: Address of Zipkin collector.
                               type: string
+                          required:
+                          - url
                           type: object
                       type: object
-                    nullable: true
                     type: array
                   sampling:
                     description: Sampling configuration. Sampling is the process by
@@ -134,6 +141,8 @@ spec:
                             name:
                               description: Name of the header.
                               type: string
+                          required:
+                          - name
                           type: object
                         literal:
                           description: Tag taken from literal value.
@@ -141,8 +150,9 @@ spec:
                         name:
                           description: Name of the tag.
                           type: string
+                      required:
+                      - name
                       type: object
-                    nullable: true
                     type: array
                 type: object
               targetRef:
diff --git a/pkg/plugins/policies/meshhttproute/api/v1alpha1/meshhttproute.go b/pkg/plugins/policies/meshhttproute/api/v1alpha1/meshhttproute.go
index 158e7a661..355c1eb1a 100644
--- a/pkg/plugins/policies/meshhttproute/api/v1alpha1/meshhttproute.go
+++ b/pkg/plugins/policies/meshhttproute/api/v1alpha1/meshhttproute.go
@@ -42,6 +42,9 @@ type Rule struct {
 type Match struct {
 	Path   *PathMatch `json:"path,omitempty"`
 	Method *Method    `json:"method,omitempty"`
+	// QueryParams matches based on HTTP URL query parameters. Multiple matches
+	// are ANDed together such that all listed matches must succeed.
+	QueryParams []QueryParamsMatch `json:"queryParams,omitempty"`
 }
 
 // +kubebuilder:validation:Enum=Exact;Prefix;RegularExpression
@@ -64,6 +67,21 @@ type PathMatch struct {
 	Type  PathMatchType `json:"type"`
 }
 
+// +kubebuilder:validation:Enum=Exact;RegularExpression
+type QueryParamsMatchType string
+
+const (
+	ExactQueryMatch             QueryParamsMatchType = "Exact"
+	RegularExpressionQueryMatch QueryParamsMatchType = "RegularExpression"
+)
+
+type QueryParamsMatch struct {
+	Type QueryParamsMatchType `json:"type"`
+	// +kubebuilder:validation:MinLength=1
+	Name  string `json:"name"`
+	Value string `json:"value"`
+}
+
 type RuleConf struct {
 	Filters     *[]Filter     `json:"filters,omitempty"`
 	BackendRefs *[]BackendRef `json:"backendRefs,omitempty"`
diff --git a/pkg/plugins/policies/meshhttproute/api/v1alpha1/schema.yaml b/pkg/plugins/policies/meshhttproute/api/v1alpha1/schema.yaml
index e7e1c9449..177bf743e 100644
--- a/pkg/plugins/policies/meshhttproute/api/v1alpha1/schema.yaml
+++ b/pkg/plugins/policies/meshhttproute/api/v1alpha1/schema.yaml
@@ -246,6 +246,26 @@ properties:
                             - type
                             - value
                           type: object
+                        queryParams:
+                          description: QueryParams matches based on HTTP URL query parameters. Multiple matches are ANDed together such that all listed matches must succeed.
+                          items:
+                            properties:
+                              name:
+                                minLength: 1
+                                type: string
+                              type:
+                                enum:
+                                  - Exact
+                                  - RegularExpression
+                                type: string
+                              value:
+                                type: string
+                            required:
+                              - name
+                              - type
+                              - value
+                            type: object
+                          type: array
                       type: object
                     type: array
                 required:
diff --git a/pkg/plugins/policies/meshhttproute/api/v1alpha1/validation.go b/pkg/plugins/policies/meshhttproute/api/v1alpha1/validation.go
index d7bc1bfd3..804bb3611 100644
--- a/pkg/plugins/policies/meshhttproute/api/v1alpha1/validation.go
+++ b/pkg/plugins/policies/meshhttproute/api/v1alpha1/validation.go
@@ -1,6 +1,7 @@
 package v1alpha1
 
 import (
+	"fmt"
 	"strings"
 
 	common_api "github.com/kumahq/kuma/api/common/v1alpha1"
@@ -66,6 +67,7 @@ func validateMatches(matches []Match) validators.ValidationError {
 		path := validators.Root().Index(i)
 		errs.AddErrorAt(path.Field("path"), validatePath(match.Path))
 		errs.AddErrorAt(path.Field("method"), validateMethod(match.Method))
+		errs.AddErrorAt(path.Field("queryParams"), validateQueryParams(match.QueryParams))
 	}
 
 	return errs
@@ -106,6 +108,21 @@ func validateMethod(match *Method) validators.ValidationError {
 	return validators.ValidationError{}
 }
 
+func validateQueryParams(matches []QueryParamsMatch) validators.ValidationError {
+	var errs validators.ValidationError
+
+	matchedNames := map[string]struct{}{}
+	for i, match := range matches {
+		if _, ok := matchedNames[match.Name]; ok {
+			path := validators.Root().Index(i).Field("name")
+			errs.AddViolationAt(path, fmt.Sprintf("multiple entries for name %s", match.Name))
+		}
+		matchedNames[match.Name] = struct{}{}
+	}
+
+	return errs
+}
+
 func validateFilters(filters *[]Filter) validators.ValidationError {
 	var errs validators.ValidationError
 
diff --git a/pkg/plugins/policies/meshhttproute/api/v1alpha1/zz_generated.deepcopy.go b/pkg/plugins/policies/meshhttproute/api/v1alpha1/zz_generated.deepcopy.go
index a32b3323a..ccbf8ed79 100644
--- a/pkg/plugins/policies/meshhttproute/api/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/plugins/policies/meshhttproute/api/v1alpha1/zz_generated.deepcopy.go
@@ -111,6 +111,11 @@ func (in *Match) DeepCopyInto(out *Match) {
 		*out = new(Method)
 		**out = **in
 	}
+	if in.QueryParams != nil {
+		in, out := &in.QueryParams, &out.QueryParams
+		*out = make([]QueryParamsMatch, len(*in))
+		copy(*out, *in)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Match.
@@ -161,6 +166,21 @@ func (in *PathMatch) DeepCopy() *PathMatch {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *QueryParamsMatch) DeepCopyInto(out *QueryParamsMatch) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QueryParamsMatch.
+func (in *QueryParamsMatch) DeepCopy() *QueryParamsMatch {
+	if in == nil {
+		return nil
+	}
+	out := new(QueryParamsMatch)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *RequestRedirect) DeepCopyInto(out *RequestRedirect) {
 	*out = *in
diff --git a/pkg/plugins/policies/meshhttproute/k8s/crd/kuma.io_meshhttproutes.yaml b/pkg/plugins/policies/meshhttproute/k8s/crd/kuma.io_meshhttproutes.yaml
index ca51a0baf..6ebbd7b21 100644
--- a/pkg/plugins/policies/meshhttproute/k8s/crd/kuma.io_meshhttproutes.yaml
+++ b/pkg/plugins/policies/meshhttproute/k8s/crd/kuma.io_meshhttproutes.yaml
@@ -304,6 +304,28 @@ spec:
                                   - type
                                   - value
                                   type: object
+                                queryParams:
+                                  description: QueryParams matches based on HTTP URL
+                                    query parameters. Multiple matches are ANDed together
+                                    such that all listed matches must succeed.
+                                  items:
+                                    properties:
+                                      name:
+                                        minLength: 1
+                                        type: string
+                                      type:
+                                        enum:
+                                        - Exact
+                                        - RegularExpression
+                                        type: string
+                                      value:
+                                        type: string
+                                    required:
+                                    - name
+                                    - type
+                                    - value
+                                    type: object
+                                  type: array
                               type: object
                             type: array
                         required:
diff --git a/pkg/plugins/policies/meshhttproute/xds/configurer.go b/pkg/plugins/policies/meshhttproute/xds/configurer.go
index b3a4fb744..00ad55500 100644
--- a/pkg/plugins/policies/meshhttproute/xds/configurer.go
+++ b/pkg/plugins/policies/meshhttproute/xds/configurer.go
@@ -56,6 +56,9 @@ func (c RoutesConfigurer) routeMatch(matches []api.Match) []*envoy_route.RouteMa
 			if match.Method != nil {
 				c.routeMethodMatch(envoyMatch, *match.Method)
 			}
+			if match.QueryParams != nil {
+				routeQueryParamsMatch(envoyMatch, match.QueryParams)
+			}
 		}
 
 		allEnvoyMatches = append(allEnvoyMatches, envoyMatches...)
@@ -93,15 +96,9 @@ func (c RoutesConfigurer) routePathMatch(match api.PathMatch) []*envoy_route.Rou
 			},
 		}}
 	case api.RegularExpression:
-		matcher := &envoy_type_matcher.RegexMatcher{
-			Regex: match.Value,
-			EngineType: &envoy_type_matcher.RegexMatcher_GoogleRe2{
-				GoogleRe2: &envoy_type_matcher.RegexMatcher_GoogleRE2{},
-			},
-		}
 		return []*envoy_route.RouteMatch{{
 			PathSpecifier: &envoy_route.RouteMatch_SafeRegex{
-				SafeRegex: matcher,
+				SafeRegex: regexMatcher(match.Value),
 			},
 		}}
 	default:
@@ -125,6 +122,46 @@ func (c RoutesConfigurer) routeMethodMatch(envoyMatch *envoy_route.RouteMatch, m
 	)
 }
 
+func routeQueryParamsMatch(envoyMatch *envoy_route.RouteMatch, matches []api.QueryParamsMatch) {
+	// We ignore multiple matchers for the same name, though this is also
+	// validated
+	matchedNames := map[string]struct{}{}
+
+	for _, match := range matches {
+		if _, ok := matchedNames[match.Name]; ok {
+			continue
+		}
+		matchedNames[match.Name] = struct{}{}
+
+		var matcher envoy_type_matcher.StringMatcher
+		switch match.Type {
+		case api.ExactQueryMatch:
+			matcher = envoy_type_matcher.StringMatcher{
+				MatchPattern: &envoy_type_matcher.StringMatcher_Exact{
+					Exact: match.Value,
+				},
+			}
+		case api.RegularExpressionQueryMatch:
+			matcher = envoy_type_matcher.StringMatcher{
+				MatchPattern: &envoy_type_matcher.StringMatcher_SafeRegex{
+					SafeRegex: regexMatcher(match.Value),
+				},
+			}
+		default:
+			panic("impossible")
+		}
+
+		envoyMatch.QueryParameters = append(envoyMatch.QueryParameters,
+			&envoy_route.QueryParameterMatcher{
+				Name: match.Name,
+				QueryParameterMatchSpecifier: &envoy_route.QueryParameterMatcher_StringMatch{
+					StringMatch: &matcher,
+				},
+			},
+		)
+	}
+}
+
 func (c RoutesConfigurer) hasExternal(clusters []envoy_common.Cluster) bool {
 	for _, cluster := range clusters {
 		if cluster.IsExternalService() {
diff --git a/pkg/plugins/policies/meshhttproute/xds/match.go b/pkg/plugins/policies/meshhttproute/xds/match.go
new file mode 100644
index 000000000..eed96b1d6
--- /dev/null
+++ b/pkg/plugins/policies/meshhttproute/xds/match.go
@@ -0,0 +1,14 @@
+package xds
+
+import (
+	envoy_type_matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
+)
+
+func regexMatcher(regex string) *envoy_type_matcher.RegexMatcher {
+	return &envoy_type_matcher.RegexMatcher{
+		Regex: regex,
+		EngineType: &envoy_type_matcher.RegexMatcher_GoogleRe2{
+			GoogleRe2: &envoy_type_matcher.RegexMatcher_GoogleRE2{},
+		},
+	}
+}
diff --git a/pkg/plugins/policies/meshtrace/api/v1alpha1/meshtrace.go b/pkg/plugins/policies/meshtrace/api/v1alpha1/meshtrace.go
index badbc2df8..61e44231c 100644
--- a/pkg/plugins/policies/meshtrace/api/v1alpha1/meshtrace.go
+++ b/pkg/plugins/policies/meshtrace/api/v1alpha1/meshtrace.go
@@ -22,18 +22,14 @@ type Conf struct {
 	// representing that would be just one object. Unfortunately due to the
 	// reasons explained in MADR 009-tracing-policy this has to be a one element
 	// array for now.
-	// +optional
-	// +nullable
-	Backends []Backend `json:"backends"`
+	Backends *[]Backend `json:"backends,omitempty"`
 	// Sampling configuration.
 	// Sampling is the process by which a decision is made on whether to
 	// process/export a span or not.
-	Sampling Sampling `json:"sampling,omitempty"`
+	Sampling *Sampling `json:"sampling,omitempty"`
 	// Custom tags configuration. You can add custom tags to traces based on
 	// headers or literal values.
-	// +optional
-	// +nullable
-	Tags []Tag `json:"tags"`
+	Tags *[]Tag `json:"tags,omitempty"`
 }
 
 // Only one of zipkin or datadog can be used.
@@ -47,13 +43,15 @@ type Backend struct {
 // Zipkin tracing backend configuration.
 type ZipkinBackend struct {
 	// Address of Zipkin collector.
-	Url string `json:"url,omitempty"`
+	Url string `json:"url"`
 	// Generate 128bit traces. Default: false
-	TraceId128Bit bool `json:"traceId128bit,omitempty"`
+	TraceId128Bit *bool `json:"traceId128bit,omitempty"`
 	// Version of the API. values: httpJson, httpProto. Default:
 	// httpJson see
 	// https://github.com/envoyproxy/envoy/blob/v1.22.0/api/envoy/config/trace/v3/zipkin.proto#L66
-	ApiVersion string `json:"apiVersion,omitempty"`
+	// +kubebuilder:default="httpJson"
+	// +kubebuilder:validation:Enum=httpJson;httpProto
+	ApiVersion *string `json:"apiVersion,omitempty"`
 	// Determines whether client and server spans will share the same span
 	// context. Default: true.
 	// https://github.com/envoyproxy/envoy/blob/v1.22.0/api/envoy/config/trace/v3/zipkin.proto#L63
@@ -64,13 +62,13 @@ type ZipkinBackend struct {
 type DatadogBackend struct {
 	// Address of Datadog collector, only host and port are allowed (no paths,
 	// fragments etc.)
-	Url string `json:"url,omitempty"`
+	Url string `json:"url"`
 	// Determines if datadog service name should be split based on traffic
 	// direction and destination. For example, with `splitService: true` and a
 	// `backend` service that communicates with a couple of databases, you would
 	// get service names like `backend_INBOUND`, `backend_OUTBOUND_db1`, and
 	// `backend_OUTBOUND_db2` in Datadog. Default: false
-	SplitService bool `json:"splitService,omitempty"`
+	SplitService *bool `json:"splitService,omitempty"`
 }
 
 // Sampling configuration.
@@ -100,9 +98,9 @@ type Sampling struct {
 // Only one of literal or header can be used.
 type Tag struct {
 	// Name of the tag.
-	Name string `json:"name,omitempty"`
+	Name string `json:"name"`
 	// Tag taken from literal value.
-	Literal string `json:"literal,omitempty"`
+	Literal *string `json:"literal,omitempty"`
 	// Tag taken from a header.
 	Header *HeaderTag `json:"header,omitempty"`
 }
@@ -110,9 +108,9 @@ type Tag struct {
 // Tag taken from a header configuration.
 type HeaderTag struct {
 	// Name of the header.
-	Name string `json:"name,omitempty"`
+	Name string `json:"name"`
 	// Default value to use if header is missing.
 	// If the default is missing and there is no value the tag will not be
 	// included.
-	Default string `json:"default,omitempty"`
+	Default *string `json:"default,omitempty"`
 }
diff --git a/pkg/plugins/policies/meshtrace/api/v1alpha1/schema.yaml b/pkg/plugins/policies/meshtrace/api/v1alpha1/schema.yaml
index 546d0e605..7b90e6dec 100644
--- a/pkg/plugins/policies/meshtrace/api/v1alpha1/schema.yaml
+++ b/pkg/plugins/policies/meshtrace/api/v1alpha1/schema.yaml
@@ -31,12 +31,18 @@ properties:
                     url:
                       description: Address of Datadog collector, only host and port are allowed (no paths, fragments etc.)
                       type: string
+                  required:
+                    - url
                   type: object
                 zipkin:
                   description: Zipkin backend configuration.
                   properties:
                     apiVersion:
+                      default: httpJson
                       description: 'Version of the API. values: httpJson, httpProto. Default: httpJson see https://github.com/envoyproxy/envoy/blob/v1.22.0/api/envoy/config/trace/v3/zipkin.proto#L66'
+                      enum:
+                        - httpJson
+                        - httpProto
                       type: string
                     sharedSpanContext:
                       description: 'Determines whether client and server spans will share the same span context. Default: true. https://github.com/envoyproxy/envoy/blob/v1.22.0/api/envoy/config/trace/v3/zipkin.proto#L63'
@@ -47,9 +53,10 @@ properties:
                     url:
                       description: Address of Zipkin collector.
                       type: string
+                  required:
+                    - url
                   type: object
               type: object
-            nullable: true
             type: array
           sampling:
             description: Sampling configuration. Sampling is the process by which a decision is made on whether to process/export a span or not.
@@ -81,6 +88,8 @@ properties:
                     name:
                       description: Name of the header.
                       type: string
+                  required:
+                    - name
                   type: object
                 literal:
                   description: Tag taken from literal value.
@@ -88,8 +97,9 @@ properties:
                 name:
                   description: Name of the tag.
                   type: string
+              required:
+                - name
               type: object
-            nullable: true
             type: array
         type: object
       targetRef:
diff --git a/pkg/plugins/policies/meshtrace/api/v1alpha1/validator.go b/pkg/plugins/policies/meshtrace/api/v1alpha1/validator.go
index 71cc08060..77bf48636 100644
--- a/pkg/plugins/policies/meshtrace/api/v1alpha1/validator.go
+++ b/pkg/plugins/policies/meshtrace/api/v1alpha1/validator.go
@@ -1,18 +1,16 @@
 package v1alpha1
 
 import (
-	"fmt"
 	"math"
 	net_url "net/url"
 	"strconv"
-	"strings"
 
 	"github.com/asaskevich/govalidator"
-	"golang.org/x/exp/slices"
 
 	common_api "github.com/kumahq/kuma/api/common/v1alpha1"
 	"github.com/kumahq/kuma/pkg/core/validators"
 	matcher_validators "github.com/kumahq/kuma/pkg/plugins/policies/matchers/validators"
+	"github.com/kumahq/kuma/pkg/util/pointer"
 )
 
 func (r *MeshTraceResource) validate() error {
@@ -22,6 +20,7 @@ func (r *MeshTraceResource) validate() error {
 	verr.AddErrorAt(path.Field("default"), validateDefault(r.Spec.Default))
 	return verr.OrNil()
 }
+
 func validateTop(targetRef common_api.TargetRef) validators.ValidationError {
 	targetRefErr := matcher_validators.ValidateTargetRef(targetRef, &matcher_validators.ValidateTargetRefOpts{
 		SupportedKinds: []common_api.TargetRefKind{
@@ -43,7 +42,7 @@ func validateDefault(conf Conf) validators.ValidationError {
 		verr.AddViolationAt(backendsPath, validators.MustBeDefined)
 	}
 
-	switch len(conf.Backends) {
+	switch len(pointer.Deref(conf.Backends)) {
 	case 0:
 		break
 	case 1:
@@ -52,25 +51,27 @@ func validateDefault(conf Conf) validators.ValidationError {
 		verr.AddViolationAt(backendsPath, "must have zero or one backend defined")
 	}
 
-	for tagIndex, tag := range conf.Tags {
+	for tagIndex, tag := range pointer.Deref(conf.Tags) {
 		path := validators.RootedAt("tags").Index(tagIndex)
 		if tag.Name == "" {
 			verr.AddViolationAt(path.Field("name"), validators.MustNotBeEmpty)
 		}
 
-		if (tag.Header != nil) == (tag.Literal != "") {
+		if (tag.Header != nil) == (tag.Literal != nil) {
 			verr.AddViolationAt(path, validators.MustHaveOnlyOne("tag", "header", "literal"))
 		}
 	}
 
-	if client := conf.Sampling.Client; client != nil {
-		verr.AddErrorAt(validators.RootedAt("sampling").Field("client"), validateSampling(*client))
-	}
-	if random := conf.Sampling.Random; random != nil {
-		verr.AddErrorAt(validators.RootedAt("sampling").Field("random"), validateSampling(*random))
-	}
-	if overall := conf.Sampling.Overall; overall != nil {
-		verr.AddErrorAt(validators.RootedAt("sampling").Field("overall"), validateSampling(*overall))
+	if conf.Sampling != nil {
+		if client := conf.Sampling.Client; client != nil {
+			verr.AddErrorAt(validators.RootedAt("sampling").Field("client"), validateSampling(*client))
+		}
+		if random := conf.Sampling.Random; random != nil {
+			verr.AddErrorAt(validators.RootedAt("sampling").Field("random"), validateSampling(*random))
+		}
+		if overall := conf.Sampling.Overall; overall != nil {
+			verr.AddErrorAt(validators.RootedAt("sampling").Field("overall"), validateSampling(*overall))
+		}
 	}
 
 	return verr
@@ -78,7 +79,7 @@ func validateDefault(conf Conf) validators.ValidationError {
 
 func validateBackend(conf Conf, backendsPath validators.PathBuilder) validators.ValidationError {
 	var verr validators.ValidationError
-	backend := conf.Backends[0]
+	backend := pointer.Deref(conf.Backends)[0]
 	firstBackendPath := backendsPath.Index(0)
 	if (backend.Datadog != nil) == (backend.Zipkin != nil) {
 		verr.AddViolationAt(firstBackendPath, validators.MustHaveOnlyOne("backend", "datadog", "zipkin"))
@@ -136,13 +137,6 @@ func validateBackend(conf Conf, backendsPath validators.PathBuilder) validators.
 		} else if !govalidator.IsURL(zipkinBackend.Url) {
 			verr.AddViolationAt(zipkinPath.Field("url"), "must be a valid url")
 		}
-
-		if zipkinBackend.ApiVersion != "" {
-			validZipkinApiVersions := []string{"httpJson", "httpProto"}
-			if !slices.Contains(validZipkinApiVersions, zipkinBackend.ApiVersion) {
-				verr.AddViolationAt(zipkinPath.Field("apiVersion"), fmt.Sprintf("must be one of %s", strings.Join(validZipkinApiVersions, ", ")))
-			}
-		}
 	}
 
 	return verr
diff --git a/pkg/plugins/policies/meshtrace/api/v1alpha1/zz_generated.deepcopy.go b/pkg/plugins/policies/meshtrace/api/v1alpha1/zz_generated.deepcopy.go
index 91f6ecf5f..4a3d12e26 100644
--- a/pkg/plugins/policies/meshtrace/api/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/plugins/policies/meshtrace/api/v1alpha1/zz_generated.deepcopy.go
@@ -18,7 +18,7 @@ func (in *Backend) DeepCopyInto(out *Backend) {
 	if in.Datadog != nil {
 		in, out := &in.Datadog, &out.Datadog
 		*out = new(DatadogBackend)
-		**out = **in
+		(*in).DeepCopyInto(*out)
 	}
 }
 
@@ -37,17 +37,29 @@ func (in *Conf) DeepCopyInto(out *Conf) {
 	*out = *in
 	if in.Backends != nil {
 		in, out := &in.Backends, &out.Backends
-		*out = make([]Backend, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
+		*out = new([]Backend)
+		if **in != nil {
+			in, out := *in, *out
+			*out = make([]Backend, len(*in))
+			for i := range *in {
+				(*in)[i].DeepCopyInto(&(*out)[i])
+			}
 		}
 	}
-	in.Sampling.DeepCopyInto(&out.Sampling)
+	if in.Sampling != nil {
+		in, out := &in.Sampling, &out.Sampling
+		*out = new(Sampling)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.Tags != nil {
 		in, out := &in.Tags, &out.Tags
-		*out = make([]Tag, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
+		*out = new([]Tag)
+		if **in != nil {
+			in, out := *in, *out
+			*out = make([]Tag, len(*in))
+			for i := range *in {
+				(*in)[i].DeepCopyInto(&(*out)[i])
+			}
 		}
 	}
 }
@@ -65,6 +77,11 @@ func (in *Conf) DeepCopy() *Conf {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *DatadogBackend) DeepCopyInto(out *DatadogBackend) {
 	*out = *in
+	if in.SplitService != nil {
+		in, out := &in.SplitService, &out.SplitService
+		*out = new(bool)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatadogBackend.
@@ -80,6 +97,11 @@ func (in *DatadogBackend) DeepCopy() *DatadogBackend {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *HeaderTag) DeepCopyInto(out *HeaderTag) {
 	*out = *in
+	if in.Default != nil {
+		in, out := &in.Default, &out.Default
+		*out = new(string)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeaderTag.
@@ -142,10 +164,15 @@ func (in *Sampling) DeepCopy() *Sampling {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Tag) DeepCopyInto(out *Tag) {
 	*out = *in
+	if in.Literal != nil {
+		in, out := &in.Literal, &out.Literal
+		*out = new(string)
+		**out = **in
+	}
 	if in.Header != nil {
 		in, out := &in.Header, &out.Header
 		*out = new(HeaderTag)
-		**out = **in
+		(*in).DeepCopyInto(*out)
 	}
 }
 
@@ -162,6 +189,16 @@ func (in *Tag) DeepCopy() *Tag {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ZipkinBackend) DeepCopyInto(out *ZipkinBackend) {
 	*out = *in
+	if in.TraceId128Bit != nil {
+		in, out := &in.TraceId128Bit, &out.TraceId128Bit
+		*out = new(bool)
+		**out = **in
+	}
+	if in.ApiVersion != nil {
+		in, out := &in.ApiVersion, &out.ApiVersion
+		*out = new(string)
+		**out = **in
+	}
 	if in.SharedSpanContext != nil {
 		in, out := &in.SharedSpanContext, &out.SharedSpanContext
 		*out = new(bool)
diff --git a/pkg/plugins/policies/meshtrace/k8s/crd/kuma.io_meshtraces.yaml b/pkg/plugins/policies/meshtrace/k8s/crd/kuma.io_meshtraces.yaml
index e0daaa2be..18104742b 100644
--- a/pkg/plugins/policies/meshtrace/k8s/crd/kuma.io_meshtraces.yaml
+++ b/pkg/plugins/policies/meshtrace/k8s/crd/kuma.io_meshtraces.yaml
@@ -64,13 +64,19 @@ spec:
                               description: Address of Datadog collector, only host
                                 and port are allowed (no paths, fragments etc.)
                               type: string
+                          required:
+                          - url
                           type: object
                         zipkin:
                           description: Zipkin backend configuration.
                           properties:
                             apiVersion:
+                              default: httpJson
                               description: 'Version of the API. values: httpJson,
                                 httpProto. Default: httpJson see https://github.com/envoyproxy/envoy/blob/v1.22.0/api/envoy/config/trace/v3/zipkin.proto#L66'
+                              enum:
+                              - httpJson
+                              - httpProto
                               type: string
                             sharedSpanContext:
                               description: 'Determines whether client and server spans
@@ -82,9 +88,10 @@ spec:
                             url:
                               description: Address of Zipkin collector.
                               type: string
+                          required:
+                          - url
                           type: object
                       type: object
-                    nullable: true
                     type: array
                   sampling:
                     description: Sampling configuration. Sampling is the process by
@@ -134,6 +141,8 @@ spec:
                             name:
                               description: Name of the header.
                               type: string
+                          required:
+                          - name
                           type: object
                         literal:
                           description: Tag taken from literal value.
@@ -141,8 +150,9 @@ spec:
                         name:
                           description: Name of the tag.
                           type: string
+                      required:
+                      - name
                       type: object
-                    nullable: true
                     type: array
                 type: object
               targetRef:
diff --git a/pkg/plugins/policies/meshtrace/plugin/v1alpha1/plugin.go b/pkg/plugins/policies/meshtrace/plugin/v1alpha1/plugin.go
index bdc728213..65c5b89dc 100644
--- a/pkg/plugins/policies/meshtrace/plugin/v1alpha1/plugin.go
+++ b/pkg/plugins/policies/meshtrace/plugin/v1alpha1/plugin.go
@@ -14,6 +14,7 @@ import (
 	api "github.com/kumahq/kuma/pkg/plugins/policies/meshtrace/api/v1alpha1"
 	plugin_xds "github.com/kumahq/kuma/pkg/plugins/policies/meshtrace/plugin/xds"
 	policies_xds "github.com/kumahq/kuma/pkg/plugins/policies/xds"
+	"github.com/kumahq/kuma/pkg/util/pointer"
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
 	"github.com/kumahq/kuma/pkg/xds/envoy/clusters"
 	xds_topology "github.com/kumahq/kuma/pkg/xds/topology"
@@ -154,7 +155,12 @@ func applyToClusters(rules xds.SingleItemRules, rs *xds.ResourceSet, proxy *xds.
 
 	conf := rawConf.(api.Conf)
 
-	backend := conf.Backends[0]
+	var backend api.Backend
+	if backends := pointer.Deref(conf.Backends); len(backends) == 0 {
+		return nil
+	} else {
+		backend = backends[0]
+	}
 
 	var endpoint *xds.Endpoint
 	var provider string
diff --git a/pkg/plugins/policies/meshtrace/plugin/xds/configurer.go b/pkg/plugins/policies/meshtrace/plugin/xds/configurer.go
index ae7d591f2..3ff792ebf 100644
--- a/pkg/plugins/policies/meshtrace/plugin/xds/configurer.go
+++ b/pkg/plugins/policies/meshtrace/plugin/xds/configurer.go
@@ -14,6 +14,7 @@ import (
 	"google.golang.org/protobuf/types/known/wrapperspb"
 
 	api "github.com/kumahq/kuma/pkg/plugins/policies/meshtrace/api/v1alpha1"
+	"github.com/kumahq/kuma/pkg/util/pointer"
 	"github.com/kumahq/kuma/pkg/util/proto"
 	"github.com/kumahq/kuma/pkg/xds/envoy"
 	v3 "github.com/kumahq/kuma/pkg/xds/envoy/listeners/v3"
@@ -37,29 +38,36 @@ const (
 )
 
 func (c *Configurer) Configure(filterChain *envoy_listener.FilterChain) error {
-	backend := c.Conf.Backends[0]
+	var backend api.Backend
+	if backends := pointer.Deref(c.Conf.Backends); len(backends) == 0 {
+		return nil
+	} else {
+		backend = backends[0]
+	}
 
 	return v3.UpdateHTTPConnectionManager(filterChain, func(hcm *envoy_hcm.HttpConnectionManager) error {
 		hcm.Tracing = &envoy_hcm.HttpConnectionManager_Tracing{}
 
-		if overall := c.Conf.Sampling.Overall; overall != nil {
-			hcm.Tracing.OverallSampling = &envoy_type.Percent{
-				Value: float64(*overall),
+		if c.Conf.Sampling != nil {
+			if overall := c.Conf.Sampling.Overall; overall != nil {
+				hcm.Tracing.OverallSampling = &envoy_type.Percent{
+					Value: float64(*overall),
+				}
 			}
-		}
-		if client := c.Conf.Sampling.Client; client != nil {
-			hcm.Tracing.ClientSampling = &envoy_type.Percent{
-				Value: float64(*client),
+			if client := c.Conf.Sampling.Client; client != nil {
+				hcm.Tracing.ClientSampling = &envoy_type.Percent{
+					Value: float64(*client),
+				}
 			}
-		}
-		if random := c.Conf.Sampling.Random; random != nil {
-			hcm.Tracing.RandomSampling = &envoy_type.Percent{
-				Value: float64(*random),
+			if random := c.Conf.Sampling.Random; random != nil {
+				hcm.Tracing.RandomSampling = &envoy_type.Percent{
+					Value: float64(*random),
+				}
 			}
 		}
 
 		if c.Conf.Tags != nil {
-			hcm.Tracing.CustomTags = mapTags(c.Conf.Tags)
+			hcm.Tracing.CustomTags = mapTags(pointer.Deref(c.Conf.Tags))
 		}
 
 		if backend.Zipkin != nil {
@@ -101,7 +109,7 @@ func (c *Configurer) datadogConfig(clusterName string) (*envoy_trace.Tracing_Htt
 }
 
 func (c *Configurer) zipkinConfig(clusterName string) (*envoy_trace.Tracing_Http, error) {
-	zipkin := c.Conf.Backends[0].Zipkin
+	zipkin := pointer.Deref(c.Conf.Backends)[0].Zipkin
 	url, err := net_url.ParseRequestURI(zipkin.Url)
 	if err != nil {
 		return nil, errors.Wrap(err, "invalid URL of Zipkin")
@@ -114,8 +122,8 @@ func (c *Configurer) zipkinConfig(clusterName string) (*envoy_trace.Tracing_Http
 	zipkinConfig := envoy_trace.ZipkinConfig{
 		CollectorCluster:         clusterName,
 		CollectorEndpoint:        url.Path,
-		TraceId_128Bit:           zipkin.TraceId128Bit,
-		CollectorEndpointVersion: apiVersion(zipkin, url),
+		TraceId_128Bit:           pointer.Deref(zipkin.TraceId128Bit),
+		CollectorEndpointVersion: apiVersion(zipkin),
 		SharedSpanContext:        ssc,
 		CollectorHostname:        url.Host,
 	}
@@ -133,9 +141,9 @@ func (c *Configurer) zipkinConfig(clusterName string) (*envoy_trace.Tracing_Http
 }
 
 func (c *Configurer) createDatadogServiceName() string {
-	datadog := c.Conf.Backends[0].Datadog
+	datadog := pointer.Deref(c.Conf.Backends)[0].Datadog
 
-	if datadog.SplitService {
+	if pointer.Deref(datadog.SplitService) {
 		var datadogServiceName []string
 		switch c.TrafficDirection {
 		case envoy_core.TrafficDirection_INBOUND:
@@ -151,20 +159,15 @@ func (c *Configurer) createDatadogServiceName() string {
 	}
 }
 
-func apiVersion(zipkin *api.ZipkinBackend, url *net_url.URL) envoy_trace.ZipkinConfig_CollectorEndpointVersion {
-	if zipkin.ApiVersion == "" { // try to infer it from the URL
-		if url.Path == "/api/v2/spans" {
-			return envoy_trace.ZipkinConfig_HTTP_JSON
-		}
-	} else {
-		switch zipkin.ApiVersion {
-		case "httpJson":
-			return envoy_trace.ZipkinConfig_HTTP_JSON
-		case "httpProto":
-			return envoy_trace.ZipkinConfig_HTTP_PROTO
-		}
+func apiVersion(zipkin *api.ZipkinBackend) envoy_trace.ZipkinConfig_CollectorEndpointVersion {
+	switch pointer.Deref(zipkin.ApiVersion) {
+	case "httpJson":
+		return envoy_trace.ZipkinConfig_HTTP_JSON
+	case "httpProto":
+		return envoy_trace.ZipkinConfig_HTTP_PROTO
+	default:
+		return envoy_trace.ZipkinConfig_HTTP_JSON
 	}
-	return envoy_trace.ZipkinConfig_HTTP_JSON
 }
 
 func mapTags(tags []api.Tag) []*tracingv3.CustomTag {
@@ -174,7 +177,7 @@ func mapTags(tags []api.Tag) []*tracingv3.CustomTag {
 		if tag.Header != nil {
 			customTags = append(customTags, mapHeaderTag(tag.Name, tag.Header))
 		} else {
-			customTags = append(customTags, mapLiteralTag(tag.Name, tag.Literal))
+			customTags = append(customTags, mapLiteralTag(tag.Name, pointer.Deref(tag.Literal)))
 		}
 	}
 
@@ -198,7 +201,7 @@ func mapHeaderTag(name string, header *api.HeaderTag) *tracingv3.CustomTag {
 		Type: &tracingv3.CustomTag_RequestHeader{
 			RequestHeader: &tracingv3.CustomTag_Header{
 				Name:         header.Name,
-				DefaultValue: header.Default,
+				DefaultValue: pointer.Deref(header.Default),
 			},
 		},
 	}


Test patch:
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml
index df583ea52..27e9830ab 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml
@@ -1119,6 +1119,28 @@ spec:
                                   - type
                                   - value
                                   type: object
+                                queryParams:
+                                  description: QueryParams matches based on HTTP URL
+                                    query parameters. Multiple matches are ANDed together
+                                    such that all listed matches must succeed.
+                                  items:
+                                    properties:
+                                      name:
+                                        minLength: 1
+                                        type: string
+                                      type:
+                                        enum:
+                                        - Exact
+                                        - RegularExpression
+                                        type: string
+                                      value:
+                                        type: string
+                                    required:
+                                    - name
+                                    - type
+                                    - value
+                                    type: object
+                                  type: array
                               type: object
                             type: array
                         required:
@@ -2419,13 +2441,19 @@ spec:
                               description: Address of Datadog collector, only host
                                 and port are allowed (no paths, fragments etc.)
                               type: string
+                          required:
+                          - url
                           type: object
                         zipkin:
                           description: Zipkin backend configuration.
                           properties:
                             apiVersion:
+                              default: httpJson
                               description: 'Version of the API. values: httpJson,
                                 httpProto. Default: httpJson see https://github.com/envoyproxy/envoy/blob/v1.22.0/api/envoy/config/trace/v3/zipkin.proto#L66'
+                              enum:
+                              - httpJson
+                              - httpProto
                               type: string
                             sharedSpanContext:
                               description: 'Determines whether client and server spans
@@ -2437,9 +2465,10 @@ spec:
                             url:
                               description: Address of Zipkin collector.
                               type: string
+                          required:
+                          - url
                           type: object
                       type: object
-                    nullable: true
                     type: array
                   sampling:
                     description: Sampling configuration. Sampling is the process by
@@ -2489,6 +2518,8 @@ spec:
                             name:
                               description: Name of the header.
                               type: string
+                          required:
+                          - name
                           type: object
                         literal:
                           description: Tag taken from literal value.
@@ -2496,8 +2527,9 @@ spec:
                         name:
                           description: Name of the tag.
                           type: string
+                      required:
+                      - name
                       type: object
-                    nullable: true
                     type: array
                 type: object
               targetRef:
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present-not-enabled.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present-not-enabled.yaml
index df583ea52..27e9830ab 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present-not-enabled.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present-not-enabled.yaml
@@ -1119,6 +1119,28 @@ spec:
                                   - type
                                   - value
                                   type: object
+                                queryParams:
+                                  description: QueryParams matches based on HTTP URL
+                                    query parameters. Multiple matches are ANDed together
+                                    such that all listed matches must succeed.
+                                  items:
+                                    properties:
+                                      name:
+                                        minLength: 1
+                                        type: string
+                                      type:
+                                        enum:
+                                        - Exact
+                                        - RegularExpression
+                                        type: string
+                                      value:
+                                        type: string
+                                    required:
+                                    - name
+                                    - type
+                                    - value
+                                    type: object
+                                  type: array
                               type: object
                             type: array
                         required:
@@ -2419,13 +2441,19 @@ spec:
                               description: Address of Datadog collector, only host
                                 and port are allowed (no paths, fragments etc.)
                               type: string
+                          required:
+                          - url
                           type: object
                         zipkin:
                           description: Zipkin backend configuration.
                           properties:
                             apiVersion:
+                              default: httpJson
                               description: 'Version of the API. values: httpJson,
                                 httpProto. Default: httpJson see https://github.com/envoyproxy/envoy/blob/v1.22.0/api/envoy/config/trace/v3/zipkin.proto#L66'
+                              enum:
+                              - httpJson
+                              - httpProto
                               type: string
                             sharedSpanContext:
                               description: 'Determines whether client and server spans
@@ -2437,9 +2465,10 @@ spec:
                             url:
                               description: Address of Zipkin collector.
                               type: string
+                          required:
+                          - url
                           type: object
                       type: object
-                    nullable: true
                     type: array
                   sampling:
                     description: Sampling configuration. Sampling is the process by
@@ -2489,6 +2518,8 @@ spec:
                             name:
                               description: Name of the header.
                               type: string
+                          required:
+                          - name
                           type: object
                         literal:
                           description: Tag taken from literal value.
@@ -2496,8 +2527,9 @@ spec:
                         name:
                           description: Name of the tag.
                           type: string
+                      required:
+                      - name
                       type: object
-                    nullable: true
                     type: array
                 type: object
               targetRef:
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml
index a96d278c0..d97e2a92b 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml
@@ -1251,6 +1251,28 @@ spec:
                                   - type
                                   - value
                                   type: object
+                                queryParams:
+                                  description: QueryParams matches based on HTTP URL
+                                    query parameters. Multiple matches are ANDed together
+                                    such that all listed matches must succeed.
+                                  items:
+                                    properties:
+                                      name:
+                                        minLength: 1
+                                        type: string
+                                      type:
+                                        enum:
+                                        - Exact
+                                        - RegularExpression
+                                        type: string
+                                      value:
+                                        type: string
+                                    required:
+                                    - name
+                                    - type
+                                    - value
+                                    type: object
+                                  type: array
                               type: object
                             type: array
                         required:
@@ -2551,13 +2573,19 @@ spec:
                               description: Address of Datadog collector, only host
                                 and port are allowed (no paths, fragments etc.)
                               type: string
+                          required:
+                          - url
                           type: object
                         zipkin:
                           description: Zipkin backend configuration.
                           properties:
                             apiVersion:
+                              default: httpJson
                               description: 'Version of the API. values: httpJson,
                                 httpProto. Default: httpJson see https://github.com/envoyproxy/envoy/blob/v1.22.0/api/envoy/config/trace/v3/zipkin.proto#L66'
+                              enum:
+                              - httpJson
+                              - httpProto
                               type: string
                             sharedSpanContext:
                               description: 'Determines whether client and server spans
@@ -2569,9 +2597,10 @@ spec:
                             url:
                               description: Address of Zipkin collector.
                               type: string
+                          required:
+                          - url
                           type: object
                       type: object
-                    nullable: true
                     type: array
                   sampling:
                     description: Sampling configuration. Sampling is the process by
@@ -2621,6 +2650,8 @@ spec:
                             name:
                               description: Name of the header.
                               type: string
+                          required:
+                          - name
                           type: object
                         literal:
                           description: Tag taken from literal value.
@@ -2628,8 +2659,9 @@ spec:
                         name:
                           description: Name of the tag.
                           type: string
+                      required:
+                      - name
                       type: object
-                    nullable: true
                     type: array
                 type: object
               targetRef:
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml
index 7eb937b28..c19b603fd 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml
@@ -1139,6 +1139,28 @@ spec:
                                   - type
                                   - value
                                   type: object
+                                queryParams:
+                                  description: QueryParams matches based on HTTP URL
+                                    query parameters. Multiple matches are ANDed together
+                                    such that all listed matches must succeed.
+                                  items:
+                                    properties:
+                                      name:
+                                        minLength: 1
+                                        type: string
+                                      type:
+                                        enum:
+                                        - Exact
+                                        - RegularExpression
+                                        type: string
+                                      value:
+                                        type: string
+                                    required:
+                                    - name
+                                    - type
+                                    - value
+                                    type: object
+                                  type: array
                               type: object
                             type: array
                         required:
@@ -2439,13 +2461,19 @@ spec:
                               description: Address of Datadog collector, only host
                                 and port are allowed (no paths, fragments etc.)
                               type: string
+                          required:
+                          - url
                           type: object
                         zipkin:
                           description: Zipkin backend configuration.
                           properties:
                             apiVersion:
+                              default: httpJson
                               description: 'Version of the API. values: httpJson,
                                 httpProto. Default: httpJson see https://github.com/envoyproxy/envoy/blob/v1.22.0/api/envoy/config/trace/v3/zipkin.proto#L66'
+                              enum:
+                              - httpJson
+                              - httpProto
                               type: string
                             sharedSpanContext:
                               description: 'Determines whether client and server spans
@@ -2457,9 +2485,10 @@ spec:
                             url:
                               description: Address of Zipkin collector.
                               type: string
+                          required:
+                          - url
                           type: object
                       type: object
-                    nullable: true
                     type: array
                   sampling:
                     description: Sampling configuration. Sampling is the process by
@@ -2509,6 +2538,8 @@ spec:
                             name:
                               description: Name of the header.
                               type: string
+                          required:
+                          - name
                           type: object
                         literal:
                           description: Tag taken from literal value.
@@ -2516,8 +2547,9 @@ spec:
                         name:
                           description: Name of the tag.
                           type: string
+                      required:
+                      - name
                       type: object
-                    nullable: true
                     type: array
                 type: object
               targetRef:
diff --git a/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml b/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml
index 0d7d41234..789eaf859 100644
--- a/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml
@@ -2290,6 +2290,28 @@ spec:
                                   - type
                                   - value
                                   type: object
+                                queryParams:
+                                  description: QueryParams matches based on HTTP URL
+                                    query parameters. Multiple matches are ANDed together
+                                    such that all listed matches must succeed.
+                                  items:
+                                    properties:
+                                      name:
+                                        minLength: 1
+                                        type: string
+                                      type:
+                                        enum:
+                                        - Exact
+                                        - RegularExpression
+                                        type: string
+                                      value:
+                                        type: string
+                                    required:
+                                    - name
+                                    - type
+                                    - value
+                                    type: object
+                                  type: array
                               type: object
                             type: array
                         required:
@@ -3545,13 +3567,19 @@ spec:
                               description: Address of Datadog collector, only host
                                 and port are allowed (no paths, fragments etc.)
                               type: string
+                          required:
+                          - url
                           type: object
                         zipkin:
                           description: Zipkin backend configuration.
                           properties:
                             apiVersion:
+                              default: httpJson
                               description: 'Version of the API. values: httpJson,
                                 httpProto. Default: httpJson see https://github.com/envoyproxy/envoy/blob/v1.22.0/api/envoy/config/trace/v3/zipkin.proto#L66'
+                              enum:
+                              - httpJson
+                              - httpProto
                               type: string
                             sharedSpanContext:
                               description: 'Determines whether client and server spans
@@ -3563,9 +3591,10 @@ spec:
                             url:
                               description: Address of Zipkin collector.
                               type: string
+                          required:
+                          - url
                           type: object
                       type: object
-                    nullable: true
                     type: array
                   sampling:
                     description: Sampling configuration. Sampling is the process by
@@ -3615,6 +3644,8 @@ spec:
                             name:
                               description: Name of the header.
                               type: string
+                          required:
+                          - name
                           type: object
                         literal:
                           description: Tag taken from literal value.
@@ -3622,8 +3653,9 @@ spec:
                         name:
                           description: Name of the tag.
                           type: string
+                      required:
+                      - name
                       type: object
-                    nullable: true
                     type: array
                 type: object
               targetRef:
diff --git a/app/kumactl/cmd/install/testdata/install-crds.experimental-gatewayapi.golden.yaml b/app/kumactl/cmd/install/testdata/install-crds.experimental-gatewayapi.golden.yaml
index a26638896..28b3e95d8 100644
--- a/app/kumactl/cmd/install/testdata/install-crds.experimental-gatewayapi.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-crds.experimental-gatewayapi.golden.yaml
@@ -2422,6 +2422,28 @@ spec:
                                   - type
                                   - value
                                   type: object
+                                queryParams:
+                                  description: QueryParams matches based on HTTP URL
+                                    query parameters. Multiple matches are ANDed together
+                                    such that all listed matches must succeed.
+                                  items:
+                                    properties:
+                                      name:
+                                        minLength: 1
+                                        type: string
+                                      type:
+                                        enum:
+                                        - Exact
+                                        - RegularExpression
+                                        type: string
+                                      value:
+                                        type: string
+                                    required:
+                                    - name
+                                    - type
+                                    - value
+                                    type: object
+                                  type: array
                               type: object
                             type: array
                         required:
@@ -3677,13 +3699,19 @@ spec:
                               description: Address of Datadog collector, only host
                                 and port are allowed (no paths, fragments etc.)
                               type: string
+                          required:
+                          - url
                           type: object
                         zipkin:
                           description: Zipkin backend configuration.
                           properties:
                             apiVersion:
+                              default: httpJson
                               description: 'Version of the API. values: httpJson,
                                 httpProto. Default: httpJson see https://github.com/envoyproxy/envoy/blob/v1.22.0/api/envoy/config/trace/v3/zipkin.proto#L66'
+                              enum:
+                              - httpJson
+                              - httpProto
                               type: string
                             sharedSpanContext:
                               description: 'Determines whether client and server spans
@@ -3695,9 +3723,10 @@ spec:
                             url:
                               description: Address of Zipkin collector.
                               type: string
+                          required:
+                          - url
                           type: object
                       type: object
-                    nullable: true
                     type: array
                   sampling:
                     description: Sampling configuration. Sampling is the process by
@@ -3747,6 +3776,8 @@ spec:
                             name:
                               description: Name of the header.
                               type: string
+                          required:
+                          - name
                           type: object
                         literal:
                           description: Tag taken from literal value.
@@ -3754,8 +3785,9 @@ spec:
                         name:
                           description: Name of the tag.
                           type: string
+                      required:
+                      - name
                       type: object
-                    nullable: true
                     type: array
                 type: object
               targetRef:
diff --git a/pkg/api-server/testdata/inspect_dataplane_rules.golden.json b/pkg/api-server/testdata/inspect_dataplane_rules.golden.json
index fc393004d..05e697c10 100644
--- a/pkg/api-server/testdata/inspect_dataplane_rules.golden.json
+++ b/pkg/api-server/testdata/inspect_dataplane_rules.golden.json
@@ -55,9 +55,7 @@
        "url": "http://jaeger-collector.mesh-observability:9411/api/v2/spans"
       }
      }
-    ],
-    "sampling": {},
-    "tags": null
+    ]
    },
    "origins": [
     {
diff --git a/pkg/core/xds/testdata/rules/06.golden.yaml b/pkg/core/xds/testdata/rules/06.golden.yaml
index f813611c9..09716fec2 100644
--- a/pkg/core/xds/testdata/rules/06.golden.yaml
+++ b/pkg/core/xds/testdata/rules/06.golden.yaml
@@ -3,7 +3,6 @@ Rules:
     backends:
     - zipkin:
         url: http://jaeger-collector.mesh-observability:9411/api/v2/spans
-    sampling: {}
     tags:
     - literal: core
       name: team
diff --git a/pkg/core/xds/testdata/rules/07.golden.yaml b/pkg/core/xds/testdata/rules/07.golden.yaml
index d6c7180df..47aee1a27 100644
--- a/pkg/core/xds/testdata/rules/07.golden.yaml
+++ b/pkg/core/xds/testdata/rules/07.golden.yaml
@@ -3,7 +3,6 @@ Rules:
     backends:
     - datadog:
         url: http://ingest.datadoghq.eu:8126
-    sampling: {}
     tags:
     - literal: support
       name: team
diff --git a/pkg/plugins/policies/meshhttproute/api/v1alpha1/validation_test.go b/pkg/plugins/policies/meshhttproute/api/v1alpha1/validation_test.go
index 16ee31291..9bb2865b0 100644
--- a/pkg/plugins/policies/meshhttproute/api/v1alpha1/validation_test.go
+++ b/pkg/plugins/policies/meshhttproute/api/v1alpha1/validation_test.go
@@ -60,6 +60,31 @@ to:
       - path:
           value: "relative"
           type: Exact
+`),
+		ErrorCase("repeated match query param names",
+			validators.Violation{
+				Field:   `spec.to[0].rules[0].matches[0].queryParams[1].name`,
+				Message: `multiple entries for name foo`,
+			}, `
+type: MeshHTTPRoute
+mesh: mesh-1
+name: route-1
+targetRef:
+  kind: MeshService
+  name: frontend
+to:
+- targetRef:
+    kind: MeshService
+    name: frontend
+  rules:
+    - matches:
+      - queryParams:
+        - type: Exact
+          name: foo
+          value: bar
+        - type: Exact
+          name: foo
+          value: baz
 `),
 		ErrorCase("invalid filter",
 			validators.Violation{
diff --git a/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/plugin_test.go b/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/plugin_test.go
index 0ec3a036e..bf4467d12 100644
--- a/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/plugin_test.go
+++ b/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/plugin_test.go
@@ -296,6 +296,20 @@ var _ = Describe("MeshHTTPRoute", func() {
 													Weight:    100,
 												}},
 											},
+										}, {
+											Matches: []api.Match{{
+												QueryParams: []api.QueryParamsMatch{{
+													Type:  api.ExactQueryMatch,
+													Name:  "v1",
+													Value: "true",
+												}},
+											}},
+											Default: api.RuleConf{
+												BackendRefs: &[]api.BackendRef{{
+													TargetRef: builders.TargetRefService("backend"),
+													Weight:    100,
+												}},
+											},
 										}}},
 								}},
 							},
diff --git a/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/testdata/basic.listeners.golden.yaml b/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/testdata/basic.listeners.golden.yaml
index 634d393c1..e9e3702b3 100644
--- a/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/testdata/basic.listeners.golden.yaml
+++ b/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/testdata/basic.listeners.golden.yaml
@@ -58,6 +58,14 @@ resources:
                 route:
                   cluster: backend-_0_
                   timeout: 0s
+              - match:
+                  queryParameters:
+                  - name: v1
+                    stringMatch:
+                      exact: "true"
+                route:
+                  cluster: backend
+                  timeout: 0s
               - match:
                   prefix: /
                 route:
diff --git a/pkg/plugins/policies/meshtrace/api/v1alpha1/validator_test.go b/pkg/plugins/policies/meshtrace/api/v1alpha1/validator_test.go
index 608b1b402..1434e69f5 100644
--- a/pkg/plugins/policies/meshtrace/api/v1alpha1/validator_test.go
+++ b/pkg/plugins/policies/meshtrace/api/v1alpha1/validator_test.go
@@ -173,22 +173,6 @@ default:
 violations:
   - field: spec.default.backends[0].zipkin.url
     message: must be a valid url`,
-			}),
-			Entry("invalid apiVersion for zipkin backend", testCase{
-				inputYaml: `
-targetRef:
-  kind: MeshService
-  name: backend
-default:
-  backends:
-    - zipkin:
-        url: http://jaeger-collector.mesh-observability:9411/api/v2/spans
-        apiVersion: invalid_api_version
-`,
-				expected: `
-violations:
-  - field: spec.default.backends[0].zipkin.apiVersion
-    message: must be one of httpJson, httpProto`,
 			}),
 			Entry("invalid url for datadog backend", testCase{
 				inputYaml: `
diff --git a/pkg/plugins/policies/meshtrace/plugin/v1alpha1/plugin_test.go b/pkg/plugins/policies/meshtrace/plugin/v1alpha1/plugin_test.go
index 29241fa0d..c308df587 100644
--- a/pkg/plugins/policies/meshtrace/plugin/v1alpha1/plugin_test.go
+++ b/pkg/plugins/policies/meshtrace/plugin/v1alpha1/plugin_test.go
@@ -29,24 +29,26 @@ var _ = Describe("MeshTrace", func() {
 		expectedListeners []string
 		expectedClusters  []string
 	}
-	inboundAndOutbound := []core_xds.Resource{
-		{
-			Name:   "inbound",
-			Origin: generator.OriginInbound,
-			Resource: NewListenerBuilder(envoy_common.APIV3).
-				Configure(InboundListener("inbound:127.0.0.1:17777", "127.0.0.1", 17777, core_xds.SocketAddressProtocolTCP)).
-				Configure(FilterChain(NewFilterChainBuilder(envoy_common.APIV3).
-					Configure(HttpConnectionManager("127.0.0.1:17777", false)),
-				)).MustBuild(),
-		}, {
-			Name:   "outbound",
-			Origin: generator.OriginOutbound,
-			Resource: NewListenerBuilder(envoy_common.APIV3).
-				Configure(OutboundListener("outbound:127.0.0.1:27777", "127.0.0.1", 27777, core_xds.SocketAddressProtocolTCP)).
-				Configure(FilterChain(NewFilterChainBuilder(envoy_common.APIV3).
-					Configure(HttpConnectionManager("127.0.0.1:27777", false)),
-				)).MustBuild(),
-		},
+	inboundAndOutbound := func() []core_xds.Resource {
+		return []core_xds.Resource{
+			{
+				Name:   "inbound",
+				Origin: generator.OriginInbound,
+				Resource: NewListenerBuilder(envoy_common.APIV3).
+					Configure(InboundListener("inbound:127.0.0.1:17777", "127.0.0.1", 17777, core_xds.SocketAddressProtocolTCP)).
+					Configure(FilterChain(NewFilterChainBuilder(envoy_common.APIV3).
+						Configure(HttpConnectionManager("127.0.0.1:17777", false)),
+					)).MustBuild(),
+			}, {
+				Name:   "outbound",
+				Origin: generator.OriginOutbound,
+				Resource: NewListenerBuilder(envoy_common.APIV3).
+					Configure(OutboundListener("outbound:127.0.0.1:27777", "127.0.0.1", 27777, core_xds.SocketAddressProtocolTCP)).
+					Configure(FilterChain(NewFilterChainBuilder(envoy_common.APIV3).
+						Configure(HttpConnectionManager("127.0.0.1:27777", false)),
+					)).MustBuild(),
+			},
+		}
 	}
 	DescribeTable("should generate proper Envoy config",
 		func(given testCase) {
@@ -103,21 +105,28 @@ var _ = Describe("MeshTrace", func() {
 			policies_xds.ResourceArrayShouldEqual(resources.ListOf(envoy_resource.ClusterType), given.expectedClusters)
 		},
 		Entry("inbound/outbound for zipkin", testCase{
-			resources: inboundAndOutbound,
+			resources: inboundAndOutbound(),
 			singleItemRules: core_xds.SingleItemRules{
 				Rules: []*core_xds.Rule{
 					{
 						Subset: []core_xds.Tag{},
 						Conf: api.Conf{
-							Sampling: api.Sampling{
-								Random: pointer.To(uint32(50)),
+							Tags: &[]api.Tag{
+								{Name: "app", Literal: pointer.To("backend")},
+								{Name: "app_code", Header: &api.HeaderTag{Name: "app_code"}},
+								{Name: "client_id", Header: &api.HeaderTag{Name: "client_id", Default: pointer.To("none")}},
+							},
+							Sampling: &api.Sampling{
+								Overall: pointer.To(uint32(10)),
+								Client:  pointer.To(uint32(20)),
+								Random:  pointer.To(uint32(50)),
 							},
-							Backends: []api.Backend{{
+							Backends: &[]api.Backend{{
 								Zipkin: &api.ZipkinBackend{
 									Url:               "http://jaeger-collector.mesh-observability:9411/api/v2/spans",
 									SharedSpanContext: pointer.To(true),
-									ApiVersion:        "httpProto",
-									TraceId128Bit:     true,
+									ApiVersion:        pointer.To("httpProto"),
+									TraceId128Bit:     pointer.To(true),
 								},
 							}},
 						},
@@ -140,6 +149,21 @@ var _ = Describe("MeshTrace", func() {
                       '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                   statPrefix: "127_0_0_1_17777"
                   tracing:
+                      clientSampling:
+                          value: 20
+                      customTags:
+                          - literal:
+                              value: backend
+                            tag: app
+                          - requestHeader:
+                              name: app_code
+                            tag: app_code
+                          - requestHeader:
+                              defaultValue: none
+                              name: client_id
+                            tag: client_id
+                      overallSampling:
+                          value: 10
                       provider:
                           name: envoy.zipkin
                           typedConfig:
@@ -169,6 +193,21 @@ var _ = Describe("MeshTrace", func() {
                       '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                   statPrefix: "127_0_0_1_27777"
                   tracing:
+                      clientSampling:
+                          value: 20
+                      customTags:
+                          - literal:
+                              value: backend
+                            tag: app
+                          - requestHeader:
+                              name: app_code
+                            tag: app_code
+                          - requestHeader:
+                              defaultValue: none
+                              name: client_id
+                            tag: client_id
+                      overallSampling:
+                          value: 10
                       provider:
                           name: envoy.zipkin
                           typedConfig:
@@ -202,19 +241,19 @@ var _ = Describe("MeshTrace", func() {
 `},
 		}),
 		Entry("inbound/outbound for datadog", testCase{
-			resources: inboundAndOutbound,
+			resources: inboundAndOutbound(),
 			singleItemRules: core_xds.SingleItemRules{
 				Rules: []*core_xds.Rule{
 					{
 						Subset: []core_xds.Tag{},
 						Conf: api.Conf{
-							Sampling: api.Sampling{
+							Sampling: &api.Sampling{
 								Random: pointer.To(uint32(50)),
 							},
-							Backends: []api.Backend{{
+							Backends: &[]api.Backend{{
 								Datadog: &api.DatadogBackend{
 									Url:          "http://ingest.datadog.eu:8126",
-									SplitService: true,
+									SplitService: pointer.To(true),
 								},
 							}},
 						},
@@ -290,5 +329,142 @@ var _ = Describe("MeshTrace", func() {
             type: STRICT_DNS
 `},
 		}),
+		Entry("sampling is empty", testCase{
+			resources: inboundAndOutbound(),
+			singleItemRules: core_xds.SingleItemRules{
+				Rules: []*core_xds.Rule{
+					{
+						Subset: []core_xds.Tag{},
+						Conf: api.Conf{
+							Backends: &[]api.Backend{{
+								Zipkin: &api.ZipkinBackend{
+									Url:               "http://jaeger-collector.mesh-observability:9411/api/v2/spans",
+									SharedSpanContext: pointer.To(true),
+									TraceId128Bit:     pointer.To(true),
+								},
+							}},
+						},
+					},
+				}},
+			expectedListeners: []string{`
+            address:
+                socketAddress:
+                    address: 127.0.0.1
+                    portValue: 17777
+            enableReusePort: false
+            filterChains:
+                - filters:
+                    - name: envoy.filters.network.http_connection_manager
+                      typedConfig:
+                        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+                        httpFilters:
+                            - name: envoy.filters.http.router
+                              typedConfig:
+                                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+                        statPrefix: "127_0_0_1_17777"
+                        tracing:
+                            provider:
+                                name: envoy.zipkin
+                                typedConfig:
+                                    '@type': type.googleapis.com/envoy.config.trace.v3.ZipkinConfig
+                                    collectorCluster: meshtrace:zipkin
+                                    collectorEndpoint: /api/v2/spans
+                                    collectorEndpointVersion: HTTP_JSON
+                                    collectorHostname: jaeger-collector.mesh-observability:9411
+                                    sharedSpanContext: true
+                                    traceId128bit: true
+            name: inbound:127.0.0.1:17777
+            trafficDirection: INBOUND`,
+				`
+            address:
+                socketAddress:
+                    address: 127.0.0.1
+                    portValue: 27777
+            filterChains:
+                - filters:
+                    - name: envoy.filters.network.http_connection_manager
+                      typedConfig:
+                        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+                        httpFilters:
+                            - name: envoy.filters.http.router
+                              typedConfig:
+                                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+                        statPrefix: "127_0_0_1_27777"
+                        tracing:
+                            provider:
+                                name: envoy.zipkin
+                                typedConfig:
+                                    '@type': type.googleapis.com/envoy.config.trace.v3.ZipkinConfig
+                                    collectorCluster: meshtrace:zipkin
+                                    collectorEndpoint: /api/v2/spans
+                                    collectorEndpointVersion: HTTP_JSON
+                                    collectorHostname: jaeger-collector.mesh-observability:9411
+                                    sharedSpanContext: true
+                                    traceId128bit: true
+            name: outbound:127.0.0.1:27777
+            trafficDirection: OUTBOUND`},
+			expectedClusters: []string{`
+            altStatName: meshtrace_zipkin
+            connectTimeout: 10s
+            dnsLookupFamily: V4_ONLY
+            loadAssignment:
+                clusterName: meshtrace:zipkin
+                endpoints:
+                    - lbEndpoints:
+                        - endpoint:
+                            address:
+                                socketAddress:
+                                    address: jaeger-collector.mesh-observability
+                                    portValue: 9411
+            name: meshtrace:zipkin
+            type: STRICT_DNS
+`},
+		}),
+		Entry("backends list is empty", testCase{
+			resources: inboundAndOutbound(),
+			singleItemRules: core_xds.SingleItemRules{
+				Rules: []*core_xds.Rule{
+					{
+						Subset: []core_xds.Tag{},
+						Conf: api.Conf{
+							Backends: &[]api.Backend{},
+						},
+					},
+				}},
+			expectedListeners: []string{`
+            address:
+                socketAddress:
+                    address: 127.0.0.1
+                    portValue: 17777
+            enableReusePort: false
+            filterChains:
+                - filters:
+                    - name: envoy.filters.network.http_connection_manager
+                      typedConfig:
+                        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+                        httpFilters:
+                            - name: envoy.filters.http.router
+                              typedConfig:
+                                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+                        statPrefix: "127_0_0_1_17777"
+            name: inbound:127.0.0.1:17777
+            trafficDirection: INBOUND`, `
+            address:
+                socketAddress:
+                    address: 127.0.0.1
+                    portValue: 27777
+            filterChains:
+                - filters:
+                    - name: envoy.filters.network.http_connection_manager
+                      typedConfig:
+                        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+                        httpFilters:
+                            - name: envoy.filters.http.router
+                              typedConfig:
+                                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+                        statPrefix: "127_0_0_1_27777"
+            name: outbound:127.0.0.1:27777
+            trafficDirection: OUTBOUND`},
+		}),
 	)
 })
diff --git a/pkg/test/resources/builders/meshtrace_builder.go b/pkg/test/resources/builders/meshtrace_builder.go
index 2348962c6..e95b60400 100644
--- a/pkg/test/resources/builders/meshtrace_builder.go
+++ b/pkg/test/resources/builders/meshtrace_builder.go
@@ -31,7 +31,7 @@ func (m *MeshTraceBuilder) WithTargetRef(targetRef common_api.TargetRef) *MeshTr
 }
 
 func (m *MeshTraceBuilder) WithZipkinBackend(zipkin *meshtrace_proto.ZipkinBackend) *MeshTraceBuilder {
-	m.res.Spec.Default.Backends = []meshtrace_proto.Backend{
+	m.res.Spec.Default.Backends = &[]meshtrace_proto.Backend{
 		{
 			Zipkin: zipkin,
 		},