Repository: kumahq/kuma
Pull request #10968 (2024-07-24 14:53:31)

chore(meshgateway): forbid possibility of setting kuma.io/service for MGI
### Checklist prior to review

In version 2.7.x we deprecate setting `kuma.io/service` tag for `MeshGatewayInstance`. In 2.9x we should be able to forbid setting it manually.
* added validation is kuma.io/service is set, and return error once is present 
* fixed e2e test after the change
* remove the warning message
* added test that validates if kuma.io/service is set
* added UPGRADE.md note

- [X] [Link to relevant issue][1] as well as docs and UI issues -- fix: https://github.com/kumahq/kuma/issues/9635
- [X] This will not break child repos: it doesn't hardcode values (.e.g "kumahq" as a image registry) and it will work on Windows, system specific functions like `syscall.Mkfifo` have equivalent implementation on the other OS --
- [X] Tests (Unit test, E2E tests, manual test on universal and k8s) --
  - Don't forget `ci/` labels to run additional/fewer tests
- [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)? ([this](https://github.com/kumahq/kuma/actions/workflows/auto-backport.yaml) GH action will add "backport" label based on these [file globs](https://github.com/kumahq/kuma/blob/master/.github/workflows/auto-backport.yaml#L6), if you want to prevent it from adding the "backport" label use [no-backport-autolabel](https://github.com/kumahq/kuma/blob/master/.github/workflows/auto-backport.yaml#L8) label) --

<!--
> Changelog: skip
-->
<!--
Uncomment the above section to explicitly set a [`> Changelog:` entry here](https://github.com/kumahq/kuma/blob/master/CONTRIBUTING.md#submitting-a-patch)?
-->

[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/UPGRADE.md b/UPGRADE.md
index 436cce4a3..70c3991f0 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -146,6 +146,34 @@ If the `--kuma-dp-user` flag is not provided, the system will attempt to use the
 
 Please update your scripts and configurations accordingly to accommodate this change.
 
+### Setting `kuma.io/service` in tags of `MeshGatewayInstance` had been forbidden
+
+To increase security, in version 2.7.x, setting a `kuma.io/service` tag for the `MeshGatewayInstance` was deprecated and since 2.9.x is not supported. We generate the `kuma.io/service` tag based on the `MeshGatewayInstance` resource. The service name is constructed as `{MeshGatewayInstance name}_{MeshGatewayInstance namespace}_svc`.
+
+E.g.:
+
+```yaml
+apiVersion: kuma.io/v1alpha1
+kind: MeshGatewayInstance
+metadata:
+  name: demo-app
+  namespace: kuma-demo
+  labels:
+    kuma.io/mesh: default
+```
+
+The generated `kuma.io/service` value is `demo-app_kuma-demo_svc`.
+
+#### Migration
+
+The migration process requires updating all policies and `MeshGateway` resources using the old `kuma.io/service` value to adopt the new one.
+
+Migration step:
+1. Create a copy of policies using the new `kuma.io/service` and the new resource name to avoid overwriting previous policies.
+2. Duplicate the `MeshGateway` resource with a selector using the new `kuma.io/service` value.
+3. Deploy the gateway and verify if traffic works correctly.
+4. Remove the old resources.
+
 ### kumactl
 
 #### Default prometheus scrape config removes `service`
diff --git a/deployments/charts/kuma/crds/kuma.io_meshgatewayinstances.yaml b/deployments/charts/kuma/crds/kuma.io_meshgatewayinstances.yaml
index 0d46d9181..afbd10ed3 100644
--- a/deployments/charts/kuma/crds/kuma.io_meshgatewayinstances.yaml
+++ b/deployments/charts/kuma/crds/kuma.io_meshgatewayinstances.yaml
@@ -201,8 +201,8 @@ spec:
                   type: string
                 description: |-
                   Tags specifies the Kuma tags that are propagated to the managed
-                  dataplane proxies. These tags should include exactly one
-                  `kuma.io/service` tag, and should match exactly one Gateway
+                  dataplane proxies. These tags should not include `kuma.io/service` tag
+                  since is auto-generated, and should match exactly one Gateway
                   resource.
                 type: object
             type: object
diff --git a/deployments/charts/kuma/crds/kuma.io_meshmultizoneservices.yaml b/deployments/charts/kuma/crds/kuma.io_meshmultizoneservices.yaml
index 3d4cb5bff..54c586d79 100644
--- a/deployments/charts/kuma/crds/kuma.io_meshmultizoneservices.yaml
+++ b/deployments/charts/kuma/crds/kuma.io_meshmultizoneservices.yaml
@@ -41,6 +41,29 @@ spec:
             description: Spec is the specification of the Kuma MeshMultiZoneService
               resource.
             properties:
+              ports:
+                description: Ports is a list of ports from selected MeshServices
+                items:
+                  properties:
+                    appProtocol:
+                      default: tcp
+                      description: Protocol identifies a protocol supported by a service.
+                      type: string
+                    name:
+                      type: string
+                    port:
+                      format: int32
+                      type: integer
+                    targetPort:
+                      anyOf:
+                      - type: integer
+                      - type: string
+                      x-kubernetes-int-or-string: true
+                  required:
+                  - port
+                  type: object
+                minItems: 1
+                type: array
               selector:
                 description: Selector is a way to select multiple MeshServices
                 properties:
@@ -158,29 +181,6 @@ spec:
                   - name
                   type: object
                 type: array
-              ports:
-                description: Ports is an aggregated distinct list of ports from selected
-                  MeshServices
-                items:
-                  properties:
-                    appProtocol:
-                      default: tcp
-                      description: Protocol identifies a protocol supported by a service.
-                      type: string
-                    name:
-                      type: string
-                    port:
-                      format: int32
-                      type: integer
-                    targetPort:
-                      anyOf:
-                      - type: integer
-                      - type: string
-                      x-kubernetes-int-or-string: true
-                  required:
-                  - port
-                  type: object
-                type: array
               vips:
                 description: VIPs is a list of assigned Kuma VIPs.
                 items:
diff --git a/docs/generated/openapi.yaml b/docs/generated/openapi.yaml
index f857d1fc2..5be1a4e59 100644
--- a/docs/generated/openapi.yaml
+++ b/docs/generated/openapi.yaml
@@ -11107,6 +11107,29 @@ components:
         spec:
           description: Spec is the specification of the Kuma MeshMultiZoneService resource.
           properties:
+            ports:
+              description: Ports is a list of ports from selected MeshServices
+              items:
+                properties:
+                  appProtocol:
+                    default: tcp
+                    description: Protocol identifies a protocol supported by a service.
+                    type: string
+                  name:
+                    type: string
+                  port:
+                    format: int32
+                    type: integer
+                  targetPort:
+                    anyOf:
+                      - type: integer
+                      - type: string
+                    x-kubernetes-int-or-string: true
+                required:
+                  - port
+                type: object
+              minItems: 1
+              type: array
             selector:
               description: Selector is a way to select multiple MeshServices
               properties:
@@ -11244,30 +11267,6 @@ components:
                   - name
                 type: object
               type: array
-            ports:
-              description: >-
-                Ports is an aggregated distinct list of ports from selected
-                MeshServices
-              items:
-                properties:
-                  appProtocol:
-                    default: tcp
-                    description: Protocol identifies a protocol supported by a service.
-                    type: string
-                  name:
-                    type: string
-                  port:
-                    format: int32
-                    type: integer
-                  targetPort:
-                    anyOf:
-                      - type: integer
-                      - type: string
-                    x-kubernetes-int-or-string: true
-                required:
-                  - port
-                type: object
-              type: array
             vips:
               description: VIPs is a list of assigned Kuma VIPs.
               items:
diff --git a/docs/generated/raw/crds/kuma.io_meshgatewayinstances.yaml b/docs/generated/raw/crds/kuma.io_meshgatewayinstances.yaml
index 0d46d9181..afbd10ed3 100644
--- a/docs/generated/raw/crds/kuma.io_meshgatewayinstances.yaml
+++ b/docs/generated/raw/crds/kuma.io_meshgatewayinstances.yaml
@@ -201,8 +201,8 @@ spec:
                   type: string
                 description: |-
                   Tags specifies the Kuma tags that are propagated to the managed
-                  dataplane proxies. These tags should include exactly one
-                  `kuma.io/service` tag, and should match exactly one Gateway
+                  dataplane proxies. These tags should not include `kuma.io/service` tag
+                  since is auto-generated, and should match exactly one Gateway
                   resource.
                 type: object
             type: object
diff --git a/docs/generated/raw/crds/kuma.io_meshmultizoneservices.yaml b/docs/generated/raw/crds/kuma.io_meshmultizoneservices.yaml
index 3d4cb5bff..54c586d79 100644
--- a/docs/generated/raw/crds/kuma.io_meshmultizoneservices.yaml
+++ b/docs/generated/raw/crds/kuma.io_meshmultizoneservices.yaml
@@ -41,6 +41,29 @@ spec:
             description: Spec is the specification of the Kuma MeshMultiZoneService
               resource.
             properties:
+              ports:
+                description: Ports is a list of ports from selected MeshServices
+                items:
+                  properties:
+                    appProtocol:
+                      default: tcp
+                      description: Protocol identifies a protocol supported by a service.
+                      type: string
+                    name:
+                      type: string
+                    port:
+                      format: int32
+                      type: integer
+                    targetPort:
+                      anyOf:
+                      - type: integer
+                      - type: string
+                      x-kubernetes-int-or-string: true
+                  required:
+                  - port
+                  type: object
+                minItems: 1
+                type: array
               selector:
                 description: Selector is a way to select multiple MeshServices
                 properties:
@@ -158,29 +181,6 @@ spec:
                   - name
                   type: object
                 type: array
-              ports:
-                description: Ports is an aggregated distinct list of ports from selected
-                  MeshServices
-                items:
-                  properties:
-                    appProtocol:
-                      default: tcp
-                      description: Protocol identifies a protocol supported by a service.
-                      type: string
-                    name:
-                      type: string
-                    port:
-                      format: int32
-                      type: integer
-                    targetPort:
-                      anyOf:
-                      - type: integer
-                      - type: string
-                      x-kubernetes-int-or-string: true
-                  required:
-                  - port
-                  type: object
-                type: array
               vips:
                 description: VIPs is a list of assigned Kuma VIPs.
                 items:
diff --git a/pkg/core/resources/apis/mesh/validators.go b/pkg/core/resources/apis/mesh/validators.go
index ebba6809c..ad77be1a8 100644
--- a/pkg/core/resources/apis/mesh/validators.go
+++ b/pkg/core/resources/apis/mesh/validators.go
@@ -36,6 +36,7 @@ type (
 type ValidateTagsOpts struct {
 	RequireAtLeastOneTag    bool
 	RequireService          bool
+	ForbidService           bool
 	ExtraTagsValidators     []TagsValidatorFunc
 	ExtraTagKeyValidators   []TagKeyValidatorFunc
 	ExtraTagValueValidators []TagValueValidatorFunc
@@ -135,6 +136,9 @@ func validateTagKeyValues(path validators.PathBuilder, keyValues map[string]stri
 		}
 	}
 	_, defined := keyValues[mesh_proto.ServiceTag]
+	if opts.ForbidService && defined {
+		err.AddViolationAt(path, fmt.Sprintf("%q must not be defined", mesh_proto.ServiceTag))
+	}
 	if opts.RequireService && !defined {
 		err.AddViolationAt(path, fmt.Sprintf("mandatory tag %q is missing", mesh_proto.ServiceTag))
 	}
diff --git a/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/helpers.go b/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/helpers.go
index 180940061..8b10a8b7c 100644
--- a/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/helpers.go
+++ b/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/helpers.go
@@ -25,7 +25,7 @@ func (t *MeshMultiZoneServiceResource) AllocateVIP(vip string) {
 }
 
 func (m *MeshMultiZoneServiceResource) FindPort(port uint32) (meshservice_api.Port, bool) {
-	for _, p := range m.Status.Ports {
+	for _, p := range m.Spec.Ports {
 		if p.Port == port {
 			return p, true
 		}
diff --git a/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/meshmultizoneservice.go b/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/meshmultizoneservice.go
index a6f3c35c5..8d1e3e781 100644
--- a/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/meshmultizoneservice.go
+++ b/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/meshmultizoneservice.go
@@ -13,6 +13,9 @@ import (
 type MeshMultiZoneService struct {
 	// Selector is a way to select multiple MeshServices
 	Selector Selector `json:"selector"`
+	// Ports is a list of ports from selected MeshServices
+	// +kubebuilder:validation:MinItems=1
+	Ports []meshservice_api.Port `json:"ports,omitempty"`
 }
 
 type Selector struct {
@@ -26,8 +29,6 @@ type MeshServiceSelector struct {
 }
 
 type MeshMultiZoneServiceStatus struct {
-	// Ports is an aggregated distinct list of ports from selected MeshServices
-	Ports []meshservice_api.Port `json:"ports,omitempty"`
 	// Addresses is a list of addresses generated by HostnameGenerator
 	Addresses []hostnamegenerator_api.Address `json:"addresses,omitempty"`
 	// VIPs is a list of assigned Kuma VIPs.
diff --git a/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/schema.yaml b/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/schema.yaml
index 4ae37cf96..c58b27173 100644
--- a/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/schema.yaml
+++ b/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/schema.yaml
@@ -21,6 +21,29 @@ properties:
   spec:
     description: Spec is the specification of the Kuma MeshMultiZoneService resource.
     properties:
+      ports:
+        description: Ports is a list of ports from selected MeshServices
+        items:
+          properties:
+            appProtocol:
+              default: tcp
+              description: Protocol identifies a protocol supported by a service.
+              type: string
+            name:
+              type: string
+            port:
+              format: int32
+              type: integer
+            targetPort:
+              anyOf:
+                - type: integer
+                - type: string
+              x-kubernetes-int-or-string: true
+          required:
+            - port
+          type: object
+        minItems: 1
+        type: array
       selector:
         description: Selector is a way to select multiple MeshServices
         properties:
@@ -135,28 +158,6 @@ properties:
             - name
           type: object
         type: array
-      ports:
-        description: Ports is an aggregated distinct list of ports from selected MeshServices
-        items:
-          properties:
-            appProtocol:
-              default: tcp
-              description: Protocol identifies a protocol supported by a service.
-              type: string
-            name:
-              type: string
-            port:
-              format: int32
-              type: integer
-            targetPort:
-              anyOf:
-                - type: integer
-                - type: string
-              x-kubernetes-int-or-string: true
-          required:
-            - port
-          type: object
-        type: array
       vips:
         description: VIPs is a list of assigned Kuma VIPs.
         items:
diff --git a/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/validator.go b/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/validator.go
index edc4a6cc6..4a2ad8d59 100644
--- a/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/validator.go
+++ b/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/validator.go
@@ -10,5 +10,8 @@ func (r *MeshMultiZoneServiceResource) validate() error {
 	if len(r.Spec.Selector.MeshService.MatchLabels) == 0 {
 		verr.AddViolationAt(path.Field("selector").Field("meshService").Field("matchLabels"), "cannot be empty")
 	}
+	if len(r.Spec.Ports) == 0 {
+		verr.AddViolationAt(path.Field("ports"), "cannot be empty")
+	}
 	return verr.OrNil()
 }
diff --git a/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/zz_generated.deepcopy.go b/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/zz_generated.deepcopy.go
index dc238787b..09eda2487 100644
--- a/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/zz_generated.deepcopy.go
@@ -28,6 +28,11 @@ func (in *MatchedMeshService) DeepCopy() *MatchedMeshService {
 func (in *MeshMultiZoneService) DeepCopyInto(out *MeshMultiZoneService) {
 	*out = *in
 	in.Selector.DeepCopyInto(&out.Selector)
+	if in.Ports != nil {
+		in, out := &in.Ports, &out.Ports
+		*out = make([]apiv1alpha1.Port, len(*in))
+		copy(*out, *in)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshMultiZoneService.
@@ -43,11 +48,6 @@ func (in *MeshMultiZoneService) DeepCopy() *MeshMultiZoneService {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *MeshMultiZoneServiceStatus) DeepCopyInto(out *MeshMultiZoneServiceStatus) {
 	*out = *in
-	if in.Ports != nil {
-		in, out := &in.Ports, &out.Ports
-		*out = make([]apiv1alpha1.Port, len(*in))
-		copy(*out, *in)
-	}
 	if in.Addresses != nil {
 		in, out := &in.Addresses, &out.Addresses
 		*out = make([]hostnamegeneratorapiv1alpha1.Address, len(*in))
diff --git a/pkg/core/resources/apis/meshmultizoneservice/k8s/crd/kuma.io_meshmultizoneservices.yaml b/pkg/core/resources/apis/meshmultizoneservice/k8s/crd/kuma.io_meshmultizoneservices.yaml
index 3d4cb5bff..54c586d79 100644
--- a/pkg/core/resources/apis/meshmultizoneservice/k8s/crd/kuma.io_meshmultizoneservices.yaml
+++ b/pkg/core/resources/apis/meshmultizoneservice/k8s/crd/kuma.io_meshmultizoneservices.yaml
@@ -41,6 +41,29 @@ spec:
             description: Spec is the specification of the Kuma MeshMultiZoneService
               resource.
             properties:
+              ports:
+                description: Ports is a list of ports from selected MeshServices
+                items:
+                  properties:
+                    appProtocol:
+                      default: tcp
+                      description: Protocol identifies a protocol supported by a service.
+                      type: string
+                    name:
+                      type: string
+                    port:
+                      format: int32
+                      type: integer
+                    targetPort:
+                      anyOf:
+                      - type: integer
+                      - type: string
+                      x-kubernetes-int-or-string: true
+                  required:
+                  - port
+                  type: object
+                minItems: 1
+                type: array
               selector:
                 description: Selector is a way to select multiple MeshServices
                 properties:
@@ -158,29 +181,6 @@ spec:
                   - name
                   type: object
                 type: array
-              ports:
-                description: Ports is an aggregated distinct list of ports from selected
-                  MeshServices
-                items:
-                  properties:
-                    appProtocol:
-                      default: tcp
-                      description: Protocol identifies a protocol supported by a service.
-                      type: string
-                    name:
-                      type: string
-                    port:
-                      format: int32
-                      type: integer
-                    targetPort:
-                      anyOf:
-                      - type: integer
-                      - type: string
-                      x-kubernetes-int-or-string: true
-                  required:
-                  - port
-                  type: object
-                type: array
               vips:
                 description: VIPs is a list of assigned Kuma VIPs.
                 items:
diff --git a/pkg/core/resources/apis/meshmultizoneservice/status_updater.go b/pkg/core/resources/apis/meshmultizoneservice/status_updater.go
index fa5cc0b0d..63e5d1adc 100644
--- a/pkg/core/resources/apis/meshmultizoneservice/status_updater.go
+++ b/pkg/core/resources/apis/meshmultizoneservice/status_updater.go
@@ -16,7 +16,6 @@ import (
 	"github.com/kumahq/kuma/pkg/core/runtime/component"
 	"github.com/kumahq/kuma/pkg/core/user"
 	core_metrics "github.com/kumahq/kuma/pkg/metrics"
-	util_maps "github.com/kumahq/kuma/pkg/util/maps"
 )
 
 type StatusUpdater struct {
@@ -102,21 +101,14 @@ func (s *StatusUpdater) updateStatus(ctx context.Context) error {
 			}
 		}
 
-		var sortedPorts []meshservice_api.Port
-		for _, port := range util_maps.SortedKeys(ports) {
-			sortedPorts = append(sortedPorts, ports[port])
-		}
-
 		sort.Slice(matched, func(i, j int) bool {
 			return matched[i].Name < matched[j].Name
 		})
 
-		if !reflect.DeepEqual(mzSvc.Status.Ports, sortedPorts) ||
-			!reflect.DeepEqual(mzSvc.Status.MeshServices, matched) {
+		if !reflect.DeepEqual(mzSvc.Status.MeshServices, matched) {
 			log := s.logger.WithValues("meshmultizoneservice", mzSvc.Meta.GetName())
-			mzSvc.Status.Ports = sortedPorts
 			mzSvc.Status.MeshServices = matched
-			log.Info("updating ports and mesh services", "matchedMeshServices", matched, "ports", sortedPorts)
+			log.Info("updating matched mesh services", "matchedMeshServices", matched)
 			if err := s.resManager.Update(ctx, mzSvc); err != nil {
 				log.Error(err, "could not update ports and mesh services")
 			}
diff --git a/pkg/plugins/resources/k8s/native/api/v1alpha1/mesh_gateway_instance.go b/pkg/plugins/resources/k8s/native/api/v1alpha1/mesh_gateway_instance.go
index d19c37b40..38a4c5b7e 100644
--- a/pkg/plugins/resources/k8s/native/api/v1alpha1/mesh_gateway_instance.go
+++ b/pkg/plugins/resources/k8s/native/api/v1alpha1/mesh_gateway_instance.go
@@ -26,8 +26,8 @@ type MeshGatewayInstanceSpec struct {
 	MeshGatewayCommonConfig `json:",inline"`
 
 	// Tags specifies the Kuma tags that are propagated to the managed
-	// dataplane proxies. These tags should include exactly one
-	// `kuma.io/service` tag, and should match exactly one Gateway
+	// dataplane proxies. These tags should not include `kuma.io/service` tag
+	// since is auto-generated, and should match exactly one Gateway
 	// resource.
 	//
 	// +required
diff --git a/pkg/plugins/runtime/k8s/controllers/gateway_instance_controller.go b/pkg/plugins/runtime/k8s/controllers/gateway_instance_controller.go
index 91c0afef6..09bb77d1a 100644
--- a/pkg/plugins/runtime/k8s/controllers/gateway_instance_controller.go
+++ b/pkg/plugins/runtime/k8s/controllers/gateway_instance_controller.go
@@ -69,11 +69,6 @@ func (r *GatewayInstanceReconciler) Reconcile(ctx context.Context, req kube_ctrl
 		return kube_ctrl.Result{}, errors.Wrap(err, "unable to get Namespace of MeshGatewayInstance")
 	}
 
-	if len(gatewayInstance.Spec.Tags) > 0 && gatewayInstance.Spec.Tags[mesh_proto.ServiceTag] != "" {
-		r.Log.Info("WARNING: Setting kuma.io/service tag is deprecated for this object kind, control-plane creates a name based on the resource name and namespace",
-			"name", gatewayInstance.GetName(), "namespace", gatewayInstance.GetNamespace(), "kind", gatewayInstance.GetObjectKind().GroupVersionKind().Kind)
-	}
-
 	mesh := k8s_util.MeshOfByLabelOrAnnotation(r.Log, gatewayInstance, &ns)
 
 	orig := gatewayInstance.DeepCopy()
@@ -106,7 +101,7 @@ func k8sSelector(name string) map[string]string {
 	return map[string]string{"app": name}
 }
 
-func (_ *GatewayInstanceReconciler) gatewayInstanceTags(gatewayInstance *mesh_k8s.MeshGatewayInstance) map[string]string {
+func (*GatewayInstanceReconciler) gatewayInstanceTags(gatewayInstance *mesh_k8s.MeshGatewayInstance) map[string]string {
 	tags := maps.Clone(gatewayInstance.Spec.Tags)
 	if tags == nil {
 		tags = map[string]string{}
@@ -420,9 +415,7 @@ func updateStatus(
 	)
 }
 
-const serviceKey string = ".metadata.service"
-
-// GatewayToInstanceMapper maps a Gateway object to MeshGatewayInstance objects by
+// GatewayToInstanceMapper maps a MeshGateway object to MeshGatewayInstance objects by
 // using the service tag to list GatewayInstances with a matching index.
 // The index is set up on MeshGatewayInstance in SetupWithManager and holds the service
 // tag from the MeshGatewayInstance tags.
@@ -447,22 +440,6 @@ func GatewayToInstanceMapper(l logr.Logger, client kube_client.Client) kube_hand
 
 		var req []kube_reconcile.Request
 		for _, serviceName := range serviceNames {
-			// TODO: remove in version 2.9.x
-			// get MeshGatewayInstance by kuma.io/service name and map to requests
-			instances := &mesh_k8s.MeshGatewayInstanceList{}
-			err := client.List(ctx, instances, kube_client.MatchingFields{serviceKey: serviceName})
-			if err != nil && !kube_apierrs.IsNotFound(err) {
-				l.Error(err, "failed to fetch GatewayInstances")
-			}
-			if len(instances.Items) > 0 {
-				for _, instance := range instances.Items {
-					req = append(req, kube_reconcile.Request{
-						NamespacedName: kube_types.NamespacedName{Namespace: instance.Namespace, Name: instance.Name},
-					})
-				}
-				continue
-			}
-
 			// map generated kuma.io/service to namespaced name
 			name, err := k8s_util.NamespacesNameFromServiceTag(serviceName)
 			if err != nil {
@@ -492,17 +469,6 @@ func (r *GatewayInstanceReconciler) SetupWithManager(mgr kube_ctrl.Manager) erro
 		return err
 	}
 
-	// TODO: remove in version 2.9.x
-	if err := mgr.GetFieldIndexer().IndexField(context.Background(), &mesh_k8s.MeshGatewayInstance{}, serviceKey, func(obj kube_client.Object) []string {
-		instance := obj.(*mesh_k8s.MeshGatewayInstance)
-
-		serviceName := instance.Spec.Tags[mesh_proto.ServiceTag]
-
-		return []string{serviceName}
-	}); err != nil {
-		return err
-	}
-
 	return kube_ctrl.NewControllerManagedBy(mgr).
 		For(&mesh_k8s.MeshGatewayInstance{}).
 		Owns(&kube_core.Service{}).
diff --git a/pkg/plugins/runtime/k8s/webhooks/gateway_instance_validator.go b/pkg/plugins/runtime/k8s/webhooks/gateway_instance_validator.go
index 7d10b8410..9d0f3ad8e 100644
--- a/pkg/plugins/runtime/k8s/webhooks/gateway_instance_validator.go
+++ b/pkg/plugins/runtime/k8s/webhooks/gateway_instance_validator.go
@@ -87,8 +87,7 @@ func (h *GatewayInstanceValidator) ValidateUpdate(ctx context.Context, req admis
 func (h *GatewayInstanceValidator) validateTags(gatewayInstance *mesh_k8s.MeshGatewayInstance) admission.Response {
 	tags := gatewayInstance.Spec.Tags
 
-	err := core_mesh.ValidateTags(validators.RootedAt("tags"), tags, core_mesh.ValidateTagsOpts{})
-
+	err := core_mesh.ValidateTags(validators.RootedAt("tags"), tags, core_mesh.ValidateTagsOpts{ForbidService: true})
 	if err.HasViolations() {
 		return convertValidationErrorOf(err, gatewayInstance, gatewayInstance.GetObjectMeta())
 	}
diff --git a/pkg/xds/generator/zoneproxy/destinations.go b/pkg/xds/generator/zoneproxy/destinations.go
index 4cc41fbb8..36fc67a51 100644
--- a/pkg/xds/generator/zoneproxy/destinations.go
+++ b/pkg/xds/generator/zoneproxy/destinations.go
@@ -74,7 +74,7 @@ func buildMeshMultiZoneServiceDestinations(
 ) []BackendRefDestination {
 	var msDestinations []BackendRefDestination
 	for _, ms := range meshMzSvc {
-		for _, port := range ms.Status.Ports {
+		for _, port := range ms.Spec.Ports {
 			msDestinations = append(msDestinations, BackendRefDestination{
 				Mesh:            ms.GetMeta().GetMesh(),
 				DestinationName: ms.DestinationName(port.Port),
diff --git a/pkg/xds/topology/dns.go b/pkg/xds/topology/dns.go
index 5ebd7ba7b..489d6904f 100644
--- a/pkg/xds/topology/dns.go
+++ b/pkg/xds/topology/dns.go
@@ -145,7 +145,7 @@ func MeshMultiZoneServiceOutbounds(services []*v1alpha1.MeshMultiZoneServiceReso
 
 	for _, svc := range services {
 		for _, vip := range svc.Status.VIPs {
-			for _, port := range svc.Status.Ports {
+			for _, port := range svc.Spec.Ports {
 				outbound := &mesh_proto.Dataplane_Networking_Outbound{
 					Address: vip.IP,
 					Port:    port.Port,
diff --git a/pkg/xds/topology/outbound.go b/pkg/xds/topology/outbound.go
index 75da54dff..2a04542a7 100644
--- a/pkg/xds/topology/outbound.go
+++ b/pkg/xds/topology/outbound.go
@@ -154,7 +154,7 @@ func fillMeshMultiZoneServices(
 				// if a new local endpoint just become healthy, we can add it immediately without waiting for state to be reconciled.
 				continue
 			}
-			for _, port := range mzSvc.Status.Ports {
+			for _, port := range mzSvc.Spec.Ports {
 				serviceName := mzSvc.DestinationName(port.Port)
 
 				existingEndpoints := outbound[ms.DestinationName(port.Port)]


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 fcddd2e3e..f3bce2f4b 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
@@ -1423,8 +1423,8 @@ spec:
                   type: string
                 description: |-
                   Tags specifies the Kuma tags that are propagated to the managed
-                  dataplane proxies. These tags should include exactly one
-                  `kuma.io/service` tag, and should match exactly one Gateway
+                  dataplane proxies. These tags should not include `kuma.io/service` tag
+                  since is auto-generated, and should match exactly one Gateway
                   resource.
                 type: object
             type: object
@@ -3739,6 +3739,29 @@ spec:
             description: Spec is the specification of the Kuma MeshMultiZoneService
               resource.
             properties:
+              ports:
+                description: Ports is a list of ports from selected MeshServices
+                items:
+                  properties:
+                    appProtocol:
+                      default: tcp
+                      description: Protocol identifies a protocol supported by a service.
+                      type: string
+                    name:
+                      type: string
+                    port:
+                      format: int32
+                      type: integer
+                    targetPort:
+                      anyOf:
+                      - type: integer
+                      - type: string
+                      x-kubernetes-int-or-string: true
+                  required:
+                  - port
+                  type: object
+                minItems: 1
+                type: array
               selector:
                 description: Selector is a way to select multiple MeshServices
                 properties:
@@ -3856,29 +3879,6 @@ spec:
                   - name
                   type: object
                 type: array
-              ports:
-                description: Ports is an aggregated distinct list of ports from selected
-                  MeshServices
-                items:
-                  properties:
-                    appProtocol:
-                      default: tcp
-                      description: Protocol identifies a protocol supported by a service.
-                      type: string
-                    name:
-                      type: string
-                    port:
-                      format: int32
-                      type: integer
-                    targetPort:
-                      anyOf:
-                      - type: integer
-                      - type: string
-                      x-kubernetes-int-or-string: true
-                  required:
-                  - port
-                  type: object
-                type: array
               vips:
                 description: VIPs is a list of assigned Kuma VIPs.
                 items:
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 ee83a6f1c..59d1360a3 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
@@ -1423,8 +1423,8 @@ spec:
                   type: string
                 description: |-
                   Tags specifies the Kuma tags that are propagated to the managed
-                  dataplane proxies. These tags should include exactly one
-                  `kuma.io/service` tag, and should match exactly one Gateway
+                  dataplane proxies. These tags should not include `kuma.io/service` tag
+                  since is auto-generated, and should match exactly one Gateway
                   resource.
                 type: object
             type: object
@@ -3739,6 +3739,29 @@ spec:
             description: Spec is the specification of the Kuma MeshMultiZoneService
               resource.
             properties:
+              ports:
+                description: Ports is a list of ports from selected MeshServices
+                items:
+                  properties:
+                    appProtocol:
+                      default: tcp
+                      description: Protocol identifies a protocol supported by a service.
+                      type: string
+                    name:
+                      type: string
+                    port:
+                      format: int32
+                      type: integer
+                    targetPort:
+                      anyOf:
+                      - type: integer
+                      - type: string
+                      x-kubernetes-int-or-string: true
+                  required:
+                  - port
+                  type: object
+                minItems: 1
+                type: array
               selector:
                 description: Selector is a way to select multiple MeshServices
                 properties:
@@ -3856,29 +3879,6 @@ spec:
                   - name
                   type: object
                 type: array
-              ports:
-                description: Ports is an aggregated distinct list of ports from selected
-                  MeshServices
-                items:
-                  properties:
-                    appProtocol:
-                      default: tcp
-                      description: Protocol identifies a protocol supported by a service.
-                      type: string
-                    name:
-                      type: string
-                    port:
-                      format: int32
-                      type: integer
-                    targetPort:
-                      anyOf:
-                      - type: integer
-                      - type: string
-                      x-kubernetes-int-or-string: true
-                  required:
-                  - port
-                  type: object
-                type: array
               vips:
                 description: VIPs is a list of assigned Kuma VIPs.
                 items:
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 20105570a..81141c1f9 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
@@ -1443,8 +1443,8 @@ spec:
                   type: string
                 description: |-
                   Tags specifies the Kuma tags that are propagated to the managed
-                  dataplane proxies. These tags should include exactly one
-                  `kuma.io/service` tag, and should match exactly one Gateway
+                  dataplane proxies. These tags should not include `kuma.io/service` tag
+                  since is auto-generated, and should match exactly one Gateway
                   resource.
                 type: object
             type: object
@@ -3759,6 +3759,29 @@ spec:
             description: Spec is the specification of the Kuma MeshMultiZoneService
               resource.
             properties:
+              ports:
+                description: Ports is a list of ports from selected MeshServices
+                items:
+                  properties:
+                    appProtocol:
+                      default: tcp
+                      description: Protocol identifies a protocol supported by a service.
+                      type: string
+                    name:
+                      type: string
+                    port:
+                      format: int32
+                      type: integer
+                    targetPort:
+                      anyOf:
+                      - type: integer
+                      - type: string
+                      x-kubernetes-int-or-string: true
+                  required:
+                  - port
+                  type: object
+                minItems: 1
+                type: array
               selector:
                 description: Selector is a way to select multiple MeshServices
                 properties:
@@ -3876,29 +3899,6 @@ spec:
                   - name
                   type: object
                 type: array
-              ports:
-                description: Ports is an aggregated distinct list of ports from selected
-                  MeshServices
-                items:
-                  properties:
-                    appProtocol:
-                      default: tcp
-                      description: Protocol identifies a protocol supported by a service.
-                      type: string
-                    name:
-                      type: string
-                    port:
-                      format: int32
-                      type: integer
-                    targetPort:
-                      anyOf:
-                      - type: integer
-                      - type: string
-                      x-kubernetes-int-or-string: true
-                  required:
-                  - port
-                  type: object
-                type: array
               vips:
                 description: VIPs is a list of assigned Kuma VIPs.
                 items:
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 4bb7ccdab..68d197426 100644
--- a/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml
@@ -3027,8 +3027,8 @@ spec:
                   type: string
                 description: |-
                   Tags specifies the Kuma tags that are propagated to the managed
-                  dataplane proxies. These tags should include exactly one
-                  `kuma.io/service` tag, and should match exactly one Gateway
+                  dataplane proxies. These tags should not include `kuma.io/service` tag
+                  since is auto-generated, and should match exactly one Gateway
                   resource.
                 type: object
             type: object
@@ -5293,6 +5293,29 @@ spec:
             description: Spec is the specification of the Kuma MeshMultiZoneService
               resource.
             properties:
+              ports:
+                description: Ports is a list of ports from selected MeshServices
+                items:
+                  properties:
+                    appProtocol:
+                      default: tcp
+                      description: Protocol identifies a protocol supported by a service.
+                      type: string
+                    name:
+                      type: string
+                    port:
+                      format: int32
+                      type: integer
+                    targetPort:
+                      anyOf:
+                      - type: integer
+                      - type: string
+                      x-kubernetes-int-or-string: true
+                  required:
+                  - port
+                  type: object
+                minItems: 1
+                type: array
               selector:
                 description: Selector is a way to select multiple MeshServices
                 properties:
@@ -5410,29 +5433,6 @@ spec:
                   - name
                   type: object
                 type: array
-              ports:
-                description: Ports is an aggregated distinct list of ports from selected
-                  MeshServices
-                items:
-                  properties:
-                    appProtocol:
-                      default: tcp
-                      description: Protocol identifies a protocol supported by a service.
-                      type: string
-                    name:
-                      type: string
-                    port:
-                      format: int32
-                      type: integer
-                    targetPort:
-                      anyOf:
-                      - type: integer
-                      - type: string
-                      x-kubernetes-int-or-string: true
-                  required:
-                  - port
-                  type: object
-                type: array
               vips:
                 description: VIPs is a list of assigned Kuma VIPs.
                 items:
diff --git a/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/validator_test.go b/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/validator_test.go
index 0d588b21b..4ecb8b869 100644
--- a/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/validator_test.go
+++ b/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1/validator_test.go
@@ -18,6 +18,10 @@ var _ = Describe("validation", func() {
 					Field:   "spec.selector.meshService.matchLabels",
 					Message: "cannot be empty",
 				},
+				{
+					Field:   "spec.ports",
+					Message: "cannot be empty",
+				},
 			},
 			``),
 	)
@@ -31,6 +35,8 @@ selector:
   meshService:
     matchLabels:
       app: xyz
+ports:
+- port: 123
 `),
 	)
 })
diff --git a/pkg/core/resources/apis/meshmultizoneservice/status_updater_test.go b/pkg/core/resources/apis/meshmultizoneservice/status_updater_test.go
index ea676c91f..f7e007c7f 100644
--- a/pkg/core/resources/apis/meshmultizoneservice/status_updater_test.go
+++ b/pkg/core/resources/apis/meshmultizoneservice/status_updater_test.go
@@ -9,10 +9,8 @@ import (
 	. "github.com/onsi/gomega"
 
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
-	core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
 	"github.com/kumahq/kuma/pkg/core/resources/apis/meshmultizoneservice"
 	meshmzservice_api "github.com/kumahq/kuma/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1"
-	meshservice_api "github.com/kumahq/kuma/pkg/core/resources/apis/meshservice/api/v1alpha1"
 	"github.com/kumahq/kuma/pkg/core/resources/manager"
 	"github.com/kumahq/kuma/pkg/core/resources/model"
 	"github.com/kumahq/kuma/pkg/core/resources/store"
@@ -48,7 +46,7 @@ var _ = Describe("Updater", func() {
 		close(stopCh)
 	})
 
-	It("should add mesh services and port to the status of multizone service", func() {
+	It("should add mesh services to the status of multizone service", func() {
 		// when
 		ms1Builder := samples.MeshServiceBackendBuilder().
 			WithName("backend").
@@ -70,7 +68,6 @@ var _ = Describe("Updater", func() {
 			err := resManager.Get(context.Background(), mzsvc, store.GetByKey("backend", model.DefaultMesh))
 			g.Expect(err).ToNot(HaveOccurred())
 			g.Expect(mzsvc.Status.MeshServices).To(Equal([]meshmzservice_api.MatchedMeshService{{Name: "backend"}}))
-			g.Expect(mzsvc.Status.Ports).To(Equal(ms1Builder.Build().Spec.Ports))
 		}, "10s", "100ms").Should(Succeed())
 
 		// when new service is added
@@ -79,7 +76,6 @@ var _ = Describe("Updater", func() {
 			WithDataplaneTagsSelector(map[string]string{
 				"app": "backend",
 			}).
-			AddIntPort(71, 8081, core_mesh.ProtocolHTTP).
 			WithLabels(map[string]string{
 				mesh_proto.DisplayName: "backend",
 				mesh_proto.ZoneTag:     "west",
@@ -96,11 +92,6 @@ var _ = Describe("Updater", func() {
 				{Name: "backend"},
 				{Name: "backend-syncedhash"},
 			}))
-			// ports are sorted
-			g.Expect(mzsvc.Status.Ports).To(Equal([]meshservice_api.Port{
-				ms2Builder.Build().Spec.Ports[1],
-				ms1Builder.Build().Spec.Ports[0],
-			}))
 		}, "10s", "100ms").Should(Succeed())
 	})
 
diff --git a/pkg/plugins/runtime/k8s/webhooks/gateway_instance_validator_test.go b/pkg/plugins/runtime/k8s/webhooks/gateway_instance_validator_test.go
index a669f7627..34c707614 100644
--- a/pkg/plugins/runtime/k8s/webhooks/gateway_instance_validator_test.go
+++ b/pkg/plugins/runtime/k8s/webhooks/gateway_instance_validator_test.go
@@ -67,15 +67,10 @@ var _ = Describe("MeshGatewayInstanceValidator", func() {
                   labels:
                     kuma.io/mesh: default
                 spec:
-                  selectors:
-                    - match:
-                        kuma.io/service: edge-gateway_kuma-demo_svc
-                  conf:
-                    listeners:
-                      - port: 8080
-                        protocol: HTTP
-                        tags:
-                          port: http/8080
+                  tags:
+                    custom.io/gateway: my-gateway
+                  replicas: 1
+                  serviceType: LoadBalancer
               operation: CREATE
 `,
 			expectedMessage: "",
@@ -100,19 +95,43 @@ var _ = Describe("MeshGatewayInstanceValidator", func() {
                   labels:
                     kuma.io/mesh: default
                 spec:
-                  selectors:
-                    - match:
-                        kuma.io/service: edge-gateway_kuma-demo_svc
-                  conf:
-                    listeners:
-                      - port: 8080
-                        protocol: HTTP
-                        tags:
-                          port: http/8080
+                  tags:
+                    custom.io/gateway: my-gateway
+                  replicas: 1
+                  serviceType: LoadBalancer
               operation: CREATE
 `,
 			expectedMessage: "Operation not allowed. Kuma resources like MeshGatewayInstance can be created only from the 'zone' control plane and not from a 'global' control plane.",
 		}),
+
+		Entry("should not allow create MeshGatewayInstance with kuma.io/service tag", testCase{
+			cpMode: core.Zone,
+			request: `
+            apiVersion: admission.k8s.io/v1
+            kind: AdmissionReview
+            request:
+              uid: 12345
+              kind:
+                group: "kuma.io"
+                kind: "MeshGatewayInstance"
+                version: v1alpha1
+              name: my-gateway
+              object:
+                apiVersion: kuma.io/v1alpha1
+                kind: MeshGatewayInstance
+                metadata:
+                  name: my-gateway
+                  labels:
+                    kuma.io/mesh: default
+                spec:
+                  tags:
+                    kuma.io/service: edge-gateway_kuma-demo_svc
+                  replicas: 1
+                  serviceType: LoadBalancer
+              operation: CREATE
+`,
+			expectedMessage: "tags: \"kuma.io/service\" must not be defined",
+		}),
 	)
 })
 
diff --git a/pkg/test/resources/builders/meshmultizoneservice_builder.go b/pkg/test/resources/builders/meshmultizoneservice_builder.go
index 1c4305c65..0827c5588 100644
--- a/pkg/test/resources/builders/meshmultizoneservice_builder.go
+++ b/pkg/test/resources/builders/meshmultizoneservice_builder.go
@@ -3,6 +3,9 @@ package builders
 import (
 	"context"
 
+	"k8s.io/apimachinery/pkg/util/intstr"
+
+	core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
 	meshmzservice_api "github.com/kumahq/kuma/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1"
 	meshservice_api "github.com/kumahq/kuma/pkg/core/resources/apis/meshservice/api/v1alpha1"
 	core_model "github.com/kumahq/kuma/pkg/core/resources/model"
@@ -55,7 +58,19 @@ func (m *MeshMultiZoneServiceBuilder) AddMatchedMeshServiceName(name string) *Me
 }
 
 func (m *MeshMultiZoneServiceBuilder) AddPort(port meshservice_api.Port) *MeshMultiZoneServiceBuilder {
-	m.res.Status.Ports = append(m.res.Status.Ports, port)
+	m.res.Spec.Ports = append(m.res.Spec.Ports, port)
+	return m
+}
+
+func (m *MeshMultiZoneServiceBuilder) AddIntPort(port, target uint32, protocol core_mesh.Protocol) *MeshMultiZoneServiceBuilder {
+	m.res.Spec.Ports = append(m.res.Spec.Ports, meshservice_api.Port{
+		Port: port,
+		TargetPort: intstr.IntOrString{
+			Type:   intstr.Int,
+			IntVal: int32(target),
+		},
+		AppProtocol: protocol,
+	})
 	return m
 }
 
diff --git a/pkg/test/resources/samples/meshmultizoneservice_samples.go b/pkg/test/resources/samples/meshmultizoneservice_samples.go
index e37e2d05a..1af76e8d7 100644
--- a/pkg/test/resources/samples/meshmultizoneservice_samples.go
+++ b/pkg/test/resources/samples/meshmultizoneservice_samples.go
@@ -7,9 +7,11 @@ import (
 )
 
 func MeshMultiZoneServiceBackendBuilder() *builders.MeshMultiZoneServiceBuilder {
-	return builders.MeshMultiZoneService().WithServiceLabelSelector(map[string]string{
-		mesh_proto.DisplayName: "backend",
-	})
+	return builders.MeshMultiZoneService().
+		WithServiceLabelSelector(map[string]string{
+			mesh_proto.DisplayName: "backend",
+		}).
+		AddIntPort(builders.FirstInboundPort, builders.FirstInboundPort, "http")
 }
 
 func MeshMultiZoneServiceBackend() *meshmzservice_api.MeshMultiZoneServiceResource {
diff --git a/pkg/xds/topology/outbound_test.go b/pkg/xds/topology/outbound_test.go
index 61353ff4d..240a885c9 100644
--- a/pkg/xds/topology/outbound_test.go
+++ b/pkg/xds/topology/outbound_test.go
@@ -1522,7 +1522,6 @@ var _ = Describe("TrafficRoute", func() {
 					samples.MeshMultiZoneServiceBackendBuilder().
 						AddMatchedMeshServiceName(samples.MeshServiceBackend().GetMeta().GetName()).
 						AddMatchedMeshServiceName(samples.MeshServiceSyncedBackend().GetMeta().GetName()).
-						AddPort(samples.MeshServiceBackend().Spec.Ports[0]).
 						Build(),
 				},
 				mesh: defaultMeshWithMTLS,
diff --git a/test/e2e_env/kubernetes/gateway/cross-mesh.go b/test/e2e_env/kubernetes/gateway/cross-mesh.go
index 6eb1a2592..21b806243 100644
--- a/test/e2e_env/kubernetes/gateway/cross-mesh.go
+++ b/test/e2e_env/kubernetes/gateway/cross-mesh.go
@@ -85,11 +85,11 @@ func CrossMeshGatewayOnKubernetes() {
 
 	Context("when mTLS is enabled", func() {
 		crossMeshGatewayYaml := mkGateway(
-			crossMeshGatewayName, crossMeshGatewayName, gatewayMesh, true, crossMeshHostname, echoServerService(gatewayMesh, gatewayTestNamespace), crossMeshGatewayPort,
+			crossMeshGatewayName, fmt.Sprintf("%s_%s_svc", crossMeshGatewayName, gatewayTestNamespace), gatewayMesh, true, crossMeshHostname, echoServerService(gatewayMesh, gatewayTestNamespace), crossMeshGatewayPort,
 		)
 		crossMeshGatewayInstanceYaml := MkGatewayInstance(crossMeshGatewayName, gatewayTestNamespace, gatewayMesh)
 		edgeGatewayYaml := mkGateway(
-			edgeGatewayName, edgeGatewayName, gatewayOtherMesh, false, "", echoServerService(gatewayOtherMesh, gatewayTestNamespace), edgeGatewayPort,
+			edgeGatewayName, fmt.Sprintf("%s_%s_svc", edgeGatewayName, gatewayTestNamespace), gatewayOtherMesh, false, "", echoServerService(gatewayOtherMesh, gatewayTestNamespace), edgeGatewayPort,
 		)
 		edgeGatewayInstanceYaml := MkGatewayInstance(
 			edgeGatewayName, gatewayTestNamespace, gatewayOtherMesh,
diff --git a/test/e2e_env/kubernetes/gateway/gateway.go b/test/e2e_env/kubernetes/gateway/gateway.go
index 8091a84c8..a16b79247 100644
--- a/test/e2e_env/kubernetes/gateway/gateway.go
+++ b/test/e2e_env/kubernetes/gateway/gateway.go
@@ -113,7 +113,7 @@ type: system.kuma.io/secret
 			)).
 			Install(YamlK8s(httpsSecret())).
 			Install(YamlK8s(meshGateway)).
-			Install(YamlK8s(MkGatewayInstanceNoServiceTag("simple-gateway", namespace, meshName))).
+			Install(YamlK8s(MkGatewayInstance("simple-gateway", namespace, meshName))).
 			Install(MeshTrafficPermissionAllowAllKubernetes(meshName)).
 			Setup(kubernetes.Cluster)
 		Expect(err).ToNot(HaveOccurred())
diff --git a/test/e2e_env/kubernetes/gateway/mtls.go b/test/e2e_env/kubernetes/gateway/mtls.go
index ee02d6ae8..10cbdf500 100644
--- a/test/e2e_env/kubernetes/gateway/mtls.go
+++ b/test/e2e_env/kubernetes/gateway/mtls.go
@@ -30,7 +30,7 @@ mesh: gateway-mtls
 spec:
   selectors:
   - match:
-      kuma.io/service: mtls-edge-gateway
+      kuma.io/service: mtls-edge-gateway_gateway-mtls_svc
   conf:
     listeners:
     - port: 8080
@@ -113,7 +113,7 @@ mesh: gateway-mtls
 spec:
   selectors:
   - match:
-      kuma.io/service: mtls-edge-gateway
+      kuma.io/service: mtls-edge-gateway_gateway-mtls_svc
       hostname: example.kuma.io
   conf:
     http:
@@ -318,8 +318,9 @@ spec:
     name: non-accessible-echo-server_gateway-mtls_svc_80
   from:
     - targetRef:
-        kind: MeshService
-        name: not-mtls-edge-gateway
+        kind: MeshSubset
+        tags:
+          kuma.io/service: not-mtls-edge-gateway_gateway-mtls_svc
       default:
         action: Allow`
 			Expect(kubernetes.Cluster.Install(YamlK8s(tp))).To(Succeed())
@@ -347,7 +348,7 @@ mesh: gateway-mtls
 spec:
   selectors:
   - match:
-      kuma.io/service: mtls-edge-gateway
+      kuma.io/service: mtls-edge-gateway_gateway-mtls_svc
       protocol: tcp
   conf:
     tcp:
@@ -393,7 +394,7 @@ mesh: gateway-mtls
 spec:
   selectors:
   - match:
-      kuma.io/service: mtls-edge-gateway
+      kuma.io/service: mtls-edge-gateway_gateway-mtls_svc
       name: tls-passthrough
   conf:
     tcp:
@@ -411,7 +412,7 @@ mesh: gateway-mtls
 spec:
   selectors:
   - match:
-      kuma.io/service: mtls-edge-gateway
+      kuma.io/service: mtls-edge-gateway_gateway-mtls_svc
       name: tls-terminate
   conf:
     tcp:
diff --git a/test/e2e_env/kubernetes/gateway/resources.go b/test/e2e_env/kubernetes/gateway/resources.go
index 5da42b963..8d2f07acb 100644
--- a/test/e2e_env/kubernetes/gateway/resources.go
+++ b/test/e2e_env/kubernetes/gateway/resources.go
@@ -32,12 +32,12 @@ mesh: %s
 spec:
   selectors:
   - match:
-      kuma.io/service: %s
+      kuma.io/service: %s_%s_svc
   conf:
     listeners:
     - port: 8080
       protocol: HTTP
-`, gatewayName, meshName, gatewayName)
+`, gatewayName, meshName, gatewayName, namespace)
 
 	meshGatewayWithLimit := fmt.Sprintf(`
 apiVersion: kuma.io/v1alpha1
@@ -48,14 +48,14 @@ mesh: %s
 spec:
   selectors:
   - match:
-      kuma.io/service: %s
+      kuma.io/service: %s_%s_svc
   conf:
     listeners:
     - port: 8080
       protocol: HTTP
       resources:
         connectionLimit: 1
-`, gatewayName, meshName, gatewayName)
+`, gatewayName, meshName, gatewayName, namespace)
 
 	serverSvc := fmt.Sprintf("test-server_%s_svc_80", namespace)
 
@@ -68,7 +68,7 @@ mesh: %s
 spec:
   selectors:
   - match:
-      kuma.io/service: %s
+      kuma.io/service: %s_%s_svc
   conf:
     http:
       rules:
@@ -79,7 +79,7 @@ spec:
         backends:
         - destination:
             kuma.io/service: %s
-`, gatewayName, meshName, gatewayName, serverSvc)
+`, gatewayName, meshName, gatewayName, namespace, serverSvc)
 
 	BeforeAll(func() {
 		err := NewClusterSetup().
diff --git a/test/e2e_env/kubernetes/gateway/utils.go b/test/e2e_env/kubernetes/gateway/utils.go
index 6a9d047e7..c7da373ce 100644
--- a/test/e2e_env/kubernetes/gateway/utils.go
+++ b/test/e2e_env/kubernetes/gateway/utils.go
@@ -98,25 +98,6 @@ func MkGatewayInstance(name, namespace, mesh string) string {
 	instance := fmt.Sprintf(`
 apiVersion: kuma.io/v1alpha1
 kind: MeshGatewayInstance
-metadata:
-  name: %s
-  namespace: %s
-  labels:
-    kuma.io/mesh: %s
-spec:
-  replicas: 1
-  serviceType: ClusterIP
-  tags:
-    kuma.io/service: %s
-`, name, namespace, mesh, name)
-
-	return instance
-}
-
-func MkGatewayInstanceNoServiceTag(name, namespace, mesh string) string {
-	instance := fmt.Sprintf(`
-apiVersion: kuma.io/v1alpha1
-kind: MeshGatewayInstance
 metadata:
   name: %s
   namespace: %s
diff --git a/test/e2e_env/kubernetes/graceful/graceful.go b/test/e2e_env/kubernetes/graceful/graceful.go
index 2f79a378f..c821a1421 100644
--- a/test/e2e_env/kubernetes/graceful/graceful.go
+++ b/test/e2e_env/kubernetes/graceful/graceful.go
@@ -46,8 +46,6 @@ metadata:
 spec:
   replicas: %d
   serviceType: LoadBalancer
-  tags:
-    kuma.io/service: edge-gateway
 `, replicas)
 	}
 
@@ -62,7 +60,7 @@ mesh: graceful
 spec:
   selectors:
   - match:
-      kuma.io/service: edge-gateway
+      kuma.io/service: edge-gateway_graceful_svc
   conf:
     listeners:
     - port: 8080
@@ -77,7 +75,7 @@ mesh: graceful
 spec:
   selectors:
   - match:
-      kuma.io/service: edge-gateway
+      kuma.io/service: edge-gateway_graceful_svc
   conf:
     http:
       rules:
diff --git a/test/e2e_env/multizone/gateway/cross-mesh.go b/test/e2e_env/multizone/gateway/cross-mesh.go
index 6e23c1f7d..8d34da4ad 100644
--- a/test/e2e_env/multizone/gateway/cross-mesh.go
+++ b/test/e2e_env/multizone/gateway/cross-mesh.go
@@ -49,6 +49,9 @@ func CrossMeshGatewayOnMultizone() {
 	const crossMeshGatewayName = "cross-mesh-gateway"
 	const edgeGatewayName = "cross-mesh-edge-gateway"
 
+	crossMeshGatewayServiceName := fmt.Sprintf("%s_%s_svc", crossMeshGatewayName, gatewayTestNamespace)
+	edgeGatewayServiceName := fmt.Sprintf("%s_%s_svc", edgeGatewayName, gatewayTestNamespace)
+
 	const gatewayMesh = "cross-mesh-gateway"
 	const gatewayOtherMesh = "cross-mesh-other"
 
@@ -65,11 +68,11 @@ func CrossMeshGatewayOnMultizone() {
 	}
 
 	crossMeshGatewayYaml := universal_gateway.MkGateway(
-		crossMeshGatewayName, gatewayMesh, true, crossMeshHostname, echoServerService(gatewayMesh, gatewayTestNamespace), crossMeshGatewayPort,
+		crossMeshGatewayName, gatewayMesh, crossMeshGatewayServiceName, true, crossMeshHostname, echoServerService(gatewayMesh, gatewayTestNamespace), crossMeshGatewayPort,
 	)
 	crossMeshGatewayInstanceYaml := k8s_gateway.MkGatewayInstance(crossMeshGatewayName, gatewayTestNamespace, gatewayMesh)
 	edgeGatewayYaml := universal_gateway.MkGateway(
-		edgeGatewayName, gatewayOtherMesh, false, "", echoServerService(gatewayOtherMesh, gatewayTestNamespace), edgeGatewayPort,
+		edgeGatewayName, gatewayOtherMesh, edgeGatewayServiceName, false, "", echoServerService(gatewayOtherMesh, gatewayTestNamespace), edgeGatewayPort,
 	)
 	edgeGatewayInstanceYaml := k8s_gateway.MkGatewayInstance(
 		edgeGatewayName, gatewayTestNamespace, gatewayOtherMesh,
@@ -148,7 +151,7 @@ func CrossMeshGatewayOnMultizone() {
 			filter := fmt.Sprintf(
 				"cluster.%s_%s.upstream_rq_total",
 				gatewayMesh,
-				crossMeshGatewayName,
+				crossMeshGatewayServiceName,
 			)
 			var currentStat stats.StatItem
 
diff --git a/test/e2e_env/multizone/meshmultizoneservice/connectivity.go b/test/e2e_env/multizone/meshmultizoneservice/connectivity.go
index 8a6da47c2..a1545b0d4 100644
--- a/test/e2e_env/multizone/meshmultizoneservice/connectivity.go
+++ b/test/e2e_env/multizone/meshmultizoneservice/connectivity.go
@@ -41,6 +41,9 @@ spec:
       matchLabels:
         kuma.io/display-name: test-server
         k8s.kuma.io/namespace: mzmsconnectivity
+  ports:
+  - port: 80
+    appProtocol: http
 `)).
 			Setup(multizone.Global)).To(Succeed())
 		Expect(WaitForMesh(meshName, multizone.Zones())).To(Succeed())
diff --git a/test/e2e_env/multizone/meshservice/sync.go b/test/e2e_env/multizone/meshservice/sync.go
index 196477a0a..557578349 100644
--- a/test/e2e_env/multizone/meshservice/sync.go
+++ b/test/e2e_env/multizone/meshservice/sync.go
@@ -5,7 +5,6 @@ import (
 
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
-	"k8s.io/apimachinery/pkg/util/intstr"
 
 	meshmzservice_api "github.com/kumahq/kuma/pkg/core/resources/apis/meshmultizoneservice/api/v1alpha1"
 	"github.com/kumahq/kuma/pkg/core/resources/apis/meshservice/api/v1alpha1"
@@ -35,6 +34,9 @@ spec:
     meshService:
       matchLabels:
         test-name: backend
+  ports:
+  - port: 80
+    appProtocol: http
 `)).
 			Setup(multizone.Global)).To(Succeed())
 		Expect(WaitForMesh(meshName, multizone.Zones())).To(Succeed())
@@ -244,14 +246,6 @@ spec:
 			status, err := mzStatus(multizone.UniZone1, "backend")
 			g.Expect(err).ToNot(HaveOccurred())
 			g.Expect(status.MeshServices).To(HaveLen(2))
-			g.Expect(status.Ports).To(Equal([]v1alpha1.Port{
-				{
-					Port:        80,
-					TargetPort:  intstr.FromInt32(80),
-					AppProtocol: "http",
-				},
-			}))
-
 			g.Expect(status.VIPs).To(HaveLen(1))
 		}, "30s", "1s").Should(Succeed())
 	})
diff --git a/test/e2e_env/universal/gateway/cross-mesh.go b/test/e2e_env/universal/gateway/cross-mesh.go
index 34a15ac83..762fbde07 100644
--- a/test/e2e_env/universal/gateway/cross-mesh.go
+++ b/test/e2e_env/universal/gateway/cross-mesh.go
@@ -44,13 +44,13 @@ func CrossMeshGatewayOnUniversal() {
 	}
 
 	crossMeshGatewayYaml := MkGateway(
-		crossMeshGatewayName, gatewayMesh, true, crossMeshHostname, echoServerName(gatewayMesh), crossMeshGatewayPort,
+		crossMeshGatewayName, gatewayMesh, crossMeshGatewayName, true, crossMeshHostname, echoServerName(gatewayMesh), crossMeshGatewayPort,
 	)
-	crossMeshGatewayDataplane := mkGatewayDataplane(crossMeshGatewayName, gatewayMesh)
+	crossMeshGatewayDataplane := mkGatewayDataplane(crossMeshGatewayName, gatewayMesh, crossMeshGatewayName)
 	edgeGatewayYaml := MkGateway(
-		edgeGatewayName, gatewayOtherMesh, false, "", echoServerName(gatewayOtherMesh), edgeGatewayPort,
+		edgeGatewayName, gatewayOtherMesh, crossMeshGatewayName, false, "", echoServerName(gatewayOtherMesh), edgeGatewayPort,
 	)
-	edgeGatewayDataplane := mkGatewayDataplane(edgeGatewayName, gatewayOtherMesh)
+	edgeGatewayDataplane := mkGatewayDataplane(edgeGatewayName, gatewayOtherMesh, crossMeshGatewayName)
 
 	BeforeAll(func() {
 		By("installing one cross-mesh gateway and one non-cross-mesh gateway")
diff --git a/test/e2e_env/universal/gateway/gateway.go b/test/e2e_env/universal/gateway/gateway.go
index d2a87bb94..e9a08d04d 100644
--- a/test/e2e_env/universal/gateway/gateway.go
+++ b/test/e2e_env/universal/gateway/gateway.go
@@ -33,9 +33,9 @@ func Gateway() {
 			Install(GatewayClientAppUniversal("gateway-client")).
 			Install(echoServerApp(mesh, "echo-server", "echo-service", "universal")).
 			Install(GatewayProxyUniversal(mesh, "gateway-proxy")).
-			Install(YamlUniversal(MkGateway("gateway-proxy", mesh, false, "example.kuma.io", "echo-service", gatewayPort))).
+			Install(YamlUniversal(MkGateway("gateway-proxy", mesh, "gateway-proxy", false, "example.kuma.io", "echo-service", gatewayPort))).
 			Install(GatewayProxyUniversal(mesh, "second-gateway-proxy")).
-			Install(YamlUniversal(MkGateway("second-gateway-proxy", mesh, false, "test.kuma.io", "echo-service", gateway2Port)))
+			Install(YamlUniversal(MkGateway("second-gateway-proxy", mesh, "second-gateway-proxy", false, "test.kuma.io", "echo-service", gateway2Port)))
 
 		Expect(setup.Setup(universal.Cluster)).To(Succeed())
 	})
diff --git a/test/e2e_env/universal/gateway/utils.go b/test/e2e_env/universal/gateway/utils.go
index eb0ae1828..9a4791a49 100644
--- a/test/e2e_env/universal/gateway/utils.go
+++ b/test/e2e_env/universal/gateway/utils.go
@@ -84,7 +84,7 @@ networking:
 `, mesh, "13000", "3000", name, "80", "8080", port, outboundService, outboundMesh)
 }
 
-func MkGateway(name, mesh string, crossMesh bool, hostname, backendService string, port int) string {
+func MkGateway(name, mesh, serviceName string, crossMesh bool, hostname, backendService string, port int) string {
 	meshGateway := fmt.Sprintf(`
 type: MeshGateway
 name: %s
@@ -98,7 +98,7 @@ conf:
     protocol: HTTP
     crossMesh: %t
     hostname: %s
-`, name, mesh, name, port, crossMesh, hostname)
+`, name, mesh, serviceName, port, crossMesh, hostname)
 
 	route := fmt.Sprintf(`
 type: MeshGatewayRoute
@@ -117,14 +117,14 @@ conf:
       backends:
       - destination:
           kuma.io/service: %s
-`, name, mesh, name, backendService)
+`, name, mesh, serviceName, backendService)
 
 	return strings.Join([]string{meshGateway, route}, "\n---\n")
 }
 
-func mkGatewayDataplane(name, mesh string) InstallFunc {
+func mkGatewayDataplane(name, mesh, serviceName string) InstallFunc {
 	return func(cluster Cluster) error {
-		token, err := universal.Cluster.GetKuma().GenerateDpToken(mesh, name)
+		token, err := universal.Cluster.GetKuma().GenerateDpToken(mesh, serviceName)
 		if err != nil {
 			return err
 		}
@@ -139,7 +139,7 @@ networking:
     type: BUILTIN
     tags:
       kuma.io/service: %s
-`, mesh, name)
+`, mesh, serviceName)
 
 		return universal.Cluster.DeployApp(
 			WithName(name),
diff --git a/test/e2e_env/universal/meshaccesslog/meshaccesslog.go b/test/e2e_env/universal/meshaccesslog/meshaccesslog.go
index 406ea7da3..50deb3c36 100644
--- a/test/e2e_env/universal/meshaccesslog/meshaccesslog.go
+++ b/test/e2e_env/universal/meshaccesslog/meshaccesslog.go
@@ -36,7 +36,7 @@ func TestPlugin() {
 				"test-server", meshName, WithArgs([]string{"echo", "--instance", "echo-v1"}), WithDockerContainerName(externalServiceDockerName)),
 			).
 			Install(GatewayProxyUniversal(meshName, "edge-gateway")).
-			Install(YamlUniversal(gateway.MkGateway("edge-gateway", meshName, false, "example.kuma.io", "test-server", 8080))).
+			Install(YamlUniversal(gateway.MkGateway("edge-gateway", meshName, "edge-gateway", false, "example.kuma.io", "test-server", 8080))).
 			Install(gateway.GatewayClientAppUniversal("gateway-client")).
 			Install(MeshTrafficPermissionAllowAllUniversal(meshName)).
 			Setup(universal.Cluster)).To(Succeed())
diff --git a/test/e2e_env/universal/observability/meshtrace.go b/test/e2e_env/universal/observability/meshtrace.go
index 7add4ae19..356a42620 100644
--- a/test/e2e_env/universal/observability/meshtrace.go
+++ b/test/e2e_env/universal/observability/meshtrace.go
@@ -51,7 +51,7 @@ func PluginTest() {
 			Install(TestServerUniversal("test-server", mesh, WithArgs([]string{"echo", "--instance", "universal1"}))).
 			Install(DemoClientUniversal(AppModeDemoClient, mesh, WithTransparentProxy(true))).
 			Install(GatewayProxyUniversal(mesh, "edge-gateway")).
-			Install(YamlUniversal(gateway.MkGateway("edge-gateway", mesh, false, "example.kuma.io", "test-server", 8080))).
+			Install(YamlUniversal(gateway.MkGateway("edge-gateway", mesh, "edge-gateway", false, "example.kuma.io", "test-server", 8080))).
 			Install(gateway.GatewayClientAppUniversal("gateway-client")).
 			Setup(universal.Cluster)
 		obsClient = obs.From(obsDeployment, universal.Cluster)