Repository: kumahq/kuma
Pull request #4300 (2022-05-17 09:29:28)

docs: add MADR process
Signed-off-by: Charly Molter <charly.molter@konghq.com>


Patch:
diff --git a/.kube-linter.yaml b/.kube-linter.yaml
new file mode 100644
index 000000000..ebf2774e6
--- /dev/null
+++ b/.kube-linter.yaml
@@ -0,0 +1,6 @@
+checks:
+  doNotAutoAddDefaults: true
+  include:
+    - "privileged-container"
+
+customChecks:
diff --git a/api/mesh/v1alpha1/kds.pb.go b/api/mesh/v1alpha1/kds.pb.go
index 082474d34..c8f429386 100644
--- a/api/mesh/v1alpha1/kds.pb.go
+++ b/api/mesh/v1alpha1/kds.pb.go
@@ -82,6 +82,178 @@ func (x *KumaResource) GetSpec() *anypb.Any {
 	return nil
 }
 
+// XDSConfigRequest is a request for XDS Config Dump that is executed on Zone
+// CP.
+type XDSConfigRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// RequestID is a UUID of a request so we can correlate requests with response
+	// on one stream.
+	RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
+	// Type of resource (Dataplane, ZoneIngress, ZoneEgress)
+	ResourceType string `protobuf:"bytes,2,opt,name=resource_type,json=resourceType,proto3" json:"resource_type,omitempty"`
+	// Name of the resource on which we execute config dump.
+	ResourceName string `protobuf:"bytes,3,opt,name=resource_name,json=resourceName,proto3" json:"resource_name,omitempty"`
+	// Mesh of the resource on which we execute config dump. Should be empty for
+	// ZoneIngress, ZoneEgress.
+	ResourceMesh string `protobuf:"bytes,4,opt,name=resource_mesh,json=resourceMesh,proto3" json:"resource_mesh,omitempty"`
+}
+
+func (x *XDSConfigRequest) Reset() {
+	*x = XDSConfigRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_mesh_v1alpha1_kds_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *XDSConfigRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*XDSConfigRequest) ProtoMessage() {}
+
+func (x *XDSConfigRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_mesh_v1alpha1_kds_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use XDSConfigRequest.ProtoReflect.Descriptor instead.
+func (*XDSConfigRequest) Descriptor() ([]byte, []int) {
+	return file_mesh_v1alpha1_kds_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *XDSConfigRequest) GetRequestId() string {
+	if x != nil {
+		return x.RequestId
+	}
+	return ""
+}
+
+func (x *XDSConfigRequest) GetResourceType() string {
+	if x != nil {
+		return x.ResourceType
+	}
+	return ""
+}
+
+func (x *XDSConfigRequest) GetResourceName() string {
+	if x != nil {
+		return x.ResourceName
+	}
+	return ""
+}
+
+func (x *XDSConfigRequest) GetResourceMesh() string {
+	if x != nil {
+		return x.ResourceMesh
+	}
+	return ""
+}
+
+// XDSConfigRequest is a response containing result of XDS Config Dump execution
+// on Zone CP.
+type XDSConfigResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// RequestID is a UUID that was set by the Global CP.
+	RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
+	// Types that are assignable to Result:
+	//	*XDSConfigResponse_Error
+	//	*XDSConfigResponse_Config
+	Result isXDSConfigResponse_Result `protobuf_oneof:"result"`
+}
+
+func (x *XDSConfigResponse) Reset() {
+	*x = XDSConfigResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_mesh_v1alpha1_kds_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *XDSConfigResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*XDSConfigResponse) ProtoMessage() {}
+
+func (x *XDSConfigResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_mesh_v1alpha1_kds_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use XDSConfigResponse.ProtoReflect.Descriptor instead.
+func (*XDSConfigResponse) Descriptor() ([]byte, []int) {
+	return file_mesh_v1alpha1_kds_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *XDSConfigResponse) GetRequestId() string {
+	if x != nil {
+		return x.RequestId
+	}
+	return ""
+}
+
+func (m *XDSConfigResponse) GetResult() isXDSConfigResponse_Result {
+	if m != nil {
+		return m.Result
+	}
+	return nil
+}
+
+func (x *XDSConfigResponse) GetError() string {
+	if x, ok := x.GetResult().(*XDSConfigResponse_Error); ok {
+		return x.Error
+	}
+	return ""
+}
+
+func (x *XDSConfigResponse) GetConfig() []byte {
+	if x, ok := x.GetResult().(*XDSConfigResponse_Config); ok {
+		return x.Config
+	}
+	return nil
+}
+
+type isXDSConfigResponse_Result interface {
+	isXDSConfigResponse_Result()
+}
+
+type XDSConfigResponse_Error struct {
+	// Error that was captured by the Zone CP when executing XDS Config Dump.
+	Error string `protobuf:"bytes,2,opt,name=error,proto3,oneof"`
+}
+
+type XDSConfigResponse_Config struct {
+	// The XDS Config that is a successful result of XDS Config DUMP execution.
+	Config []byte `protobuf:"bytes,3,opt,name=config,proto3,oneof"`
+}
+
+func (*XDSConfigResponse_Error) isXDSConfigResponse_Result() {}
+
+func (*XDSConfigResponse_Config) isXDSConfigResponse_Result() {}
+
 type KumaResource_Meta struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -97,7 +269,7 @@ type KumaResource_Meta struct {
 func (x *KumaResource_Meta) Reset() {
 	*x = KumaResource_Meta{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_mesh_v1alpha1_kds_proto_msgTypes[1]
+		mi := &file_mesh_v1alpha1_kds_proto_msgTypes[3]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -110,7 +282,7 @@ func (x *KumaResource_Meta) String() string {
 func (*KumaResource_Meta) ProtoMessage() {}
 
 func (x *KumaResource_Meta) ProtoReflect() protoreflect.Message {
-	mi := &file_mesh_v1alpha1_kds_proto_msgTypes[1]
+	mi := &file_mesh_v1alpha1_kds_proto_msgTypes[3]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -194,19 +366,44 @@ var file_mesh_v1alpha1_kds_proto_rawDesc = []byte{
 	0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
 	0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
 	0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
-	0x32, 0x8e, 0x01, 0x0a, 0x14, 0x4b, 0x75, 0x6d, 0x61, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65,
-	0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x76, 0x0a, 0x13, 0x53, 0x74, 0x72,
-	0x65, 0x61, 0x6d, 0x4b, 0x75, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73,
-	0x12, 0x2c, 0x2e, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
-	0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x76, 0x33, 0x2e, 0x44, 0x69,
-	0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d,
-	0x2e, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x64,
-	0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x76, 0x33, 0x2e, 0x44, 0x69, 0x73, 0x63,
-	0x6f, 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30,
-	0x01, 0x42, 0x2a, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
-	0x6b, 0x75, 0x6d, 0x61, 0x68, 0x71, 0x2f, 0x6b, 0x75, 0x6d, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f,
-	0x6d, 0x65, 0x73, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x22, 0xa0, 0x01, 0x0a, 0x10, 0x58, 0x44, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
+	0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73,
+	0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73,
+	0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23,
+	0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x68, 0x18,
+	0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d,
+	0x65, 0x73, 0x68, 0x22, 0x6e, 0x0a, 0x11, 0x58, 0x44, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12,
+	0x18, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x48,
+	0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x08, 0x0a, 0x06, 0x72, 0x65, 0x73,
+	0x75, 0x6c, 0x74, 0x32, 0x8e, 0x01, 0x0a, 0x14, 0x4b, 0x75, 0x6d, 0x61, 0x44, 0x69, 0x73, 0x63,
+	0x6f, 0x76, 0x65, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x76, 0x0a, 0x13,
+	0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4b, 0x75, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x73, 0x12, 0x2c, 0x2e, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x2e, 0x73, 0x65, 0x72, 0x76,
+	0x69, 0x63, 0x65, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x76, 0x33,
+	0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x1a, 0x2d, 0x2e, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
+	0x65, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x76, 0x33, 0x2e, 0x44,
+	0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x28, 0x01, 0x30, 0x01, 0x32, 0x77, 0x0a, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4b, 0x44,
+	0x53, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x63, 0x0a, 0x10, 0x53, 0x74, 0x72, 0x65,
+	0x61, 0x6d, 0x58, 0x44, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x25, 0x2e, 0x6b,
+	0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61,
+	0x31, 0x2e, 0x58, 0x44, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f,
+	0x6e, 0x73, 0x65, 0x1a, 0x24, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e,
+	0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x58, 0x44, 0x53, 0x43, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x42, 0x2a, 0x5a,
+	0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x75, 0x6d, 0x61,
+	0x68, 0x71, 0x2f, 0x6b, 0x75, 0x6d, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6d, 0x65, 0x73, 0x68,
+	0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x33,
 }
 
 var (
@@ -221,24 +418,28 @@ func file_mesh_v1alpha1_kds_proto_rawDescGZIP() []byte {
 	return file_mesh_v1alpha1_kds_proto_rawDescData
 }
 
-var file_mesh_v1alpha1_kds_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_mesh_v1alpha1_kds_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
 var file_mesh_v1alpha1_kds_proto_goTypes = []interface{}{
 	(*KumaResource)(nil),          // 0: kuma.mesh.v1alpha1.KumaResource
-	(*KumaResource_Meta)(nil),     // 1: kuma.mesh.v1alpha1.KumaResource.Meta
-	(*anypb.Any)(nil),             // 2: google.protobuf.Any
-	(*timestamppb.Timestamp)(nil), // 3: google.protobuf.Timestamp
-	(*v3.DiscoveryRequest)(nil),   // 4: envoy.service.discovery.v3.DiscoveryRequest
-	(*v3.DiscoveryResponse)(nil),  // 5: envoy.service.discovery.v3.DiscoveryResponse
+	(*XDSConfigRequest)(nil),      // 1: kuma.mesh.v1alpha1.XDSConfigRequest
+	(*XDSConfigResponse)(nil),     // 2: kuma.mesh.v1alpha1.XDSConfigResponse
+	(*KumaResource_Meta)(nil),     // 3: kuma.mesh.v1alpha1.KumaResource.Meta
+	(*anypb.Any)(nil),             // 4: google.protobuf.Any
+	(*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp
+	(*v3.DiscoveryRequest)(nil),   // 6: envoy.service.discovery.v3.DiscoveryRequest
+	(*v3.DiscoveryResponse)(nil),  // 7: envoy.service.discovery.v3.DiscoveryResponse
 }
 var file_mesh_v1alpha1_kds_proto_depIdxs = []int32{
-	1, // 0: kuma.mesh.v1alpha1.KumaResource.meta:type_name -> kuma.mesh.v1alpha1.KumaResource.Meta
-	2, // 1: kuma.mesh.v1alpha1.KumaResource.spec:type_name -> google.protobuf.Any
-	3, // 2: kuma.mesh.v1alpha1.KumaResource.Meta.creation_time:type_name -> google.protobuf.Timestamp
-	3, // 3: kuma.mesh.v1alpha1.KumaResource.Meta.modification_time:type_name -> google.protobuf.Timestamp
-	4, // 4: kuma.mesh.v1alpha1.KumaDiscoveryService.StreamKumaResources:input_type -> envoy.service.discovery.v3.DiscoveryRequest
-	5, // 5: kuma.mesh.v1alpha1.KumaDiscoveryService.StreamKumaResources:output_type -> envoy.service.discovery.v3.DiscoveryResponse
-	5, // [5:6] is the sub-list for method output_type
-	4, // [4:5] is the sub-list for method input_type
+	3, // 0: kuma.mesh.v1alpha1.KumaResource.meta:type_name -> kuma.mesh.v1alpha1.KumaResource.Meta
+	4, // 1: kuma.mesh.v1alpha1.KumaResource.spec:type_name -> google.protobuf.Any
+	5, // 2: kuma.mesh.v1alpha1.KumaResource.Meta.creation_time:type_name -> google.protobuf.Timestamp
+	5, // 3: kuma.mesh.v1alpha1.KumaResource.Meta.modification_time:type_name -> google.protobuf.Timestamp
+	6, // 4: kuma.mesh.v1alpha1.KumaDiscoveryService.StreamKumaResources:input_type -> envoy.service.discovery.v3.DiscoveryRequest
+	2, // 5: kuma.mesh.v1alpha1.GlobalKDSService.StreamXDSConfigs:input_type -> kuma.mesh.v1alpha1.XDSConfigResponse
+	7, // 6: kuma.mesh.v1alpha1.KumaDiscoveryService.StreamKumaResources:output_type -> envoy.service.discovery.v3.DiscoveryResponse
+	1, // 7: kuma.mesh.v1alpha1.GlobalKDSService.StreamXDSConfigs:output_type -> kuma.mesh.v1alpha1.XDSConfigRequest
+	6, // [6:8] is the sub-list for method output_type
+	4, // [4:6] is the sub-list for method input_type
 	4, // [4:4] is the sub-list for extension type_name
 	4, // [4:4] is the sub-list for extension extendee
 	0, // [0:4] is the sub-list for field type_name
@@ -263,6 +464,30 @@ func file_mesh_v1alpha1_kds_proto_init() {
 			}
 		}
 		file_mesh_v1alpha1_kds_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*XDSConfigRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_mesh_v1alpha1_kds_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*XDSConfigResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_mesh_v1alpha1_kds_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*KumaResource_Meta); i {
 			case 0:
 				return &v.state
@@ -275,15 +500,19 @@ func file_mesh_v1alpha1_kds_proto_init() {
 			}
 		}
 	}
+	file_mesh_v1alpha1_kds_proto_msgTypes[2].OneofWrappers = []interface{}{
+		(*XDSConfigResponse_Error)(nil),
+		(*XDSConfigResponse_Config)(nil),
+	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_mesh_v1alpha1_kds_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   2,
+			NumMessages:   4,
 			NumExtensions: 0,
-			NumServices:   1,
+			NumServices:   2,
 		},
 		GoTypes:           file_mesh_v1alpha1_kds_proto_goTypes,
 		DependencyIndexes: file_mesh_v1alpha1_kds_proto_depIdxs,
@@ -406,3 +635,113 @@ var _KumaDiscoveryService_serviceDesc = grpc.ServiceDesc{
 	},
 	Metadata: "mesh/v1alpha1/kds.proto",
 }
+
+// GlobalKDSServiceClient is the client API for GlobalKDSService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
+type GlobalKDSServiceClient interface {
+	// StreamXDSConfigs is logically a service exposed by Zone CP so Global CP can
+	// execute Config Dumps. It is however represented by bi-directional streaming
+	// to leverage existing connection from Zone CP to Global CP.
+	StreamXDSConfigs(ctx context.Context, opts ...grpc.CallOption) (GlobalKDSService_StreamXDSConfigsClient, error)
+}
+
+type globalKDSServiceClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewGlobalKDSServiceClient(cc grpc.ClientConnInterface) GlobalKDSServiceClient {
+	return &globalKDSServiceClient{cc}
+}
+
+func (c *globalKDSServiceClient) StreamXDSConfigs(ctx context.Context, opts ...grpc.CallOption) (GlobalKDSService_StreamXDSConfigsClient, error) {
+	stream, err := c.cc.NewStream(ctx, &_GlobalKDSService_serviceDesc.Streams[0], "/kuma.mesh.v1alpha1.GlobalKDSService/StreamXDSConfigs", opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &globalKDSServiceStreamXDSConfigsClient{stream}
+	return x, nil
+}
+
+type GlobalKDSService_StreamXDSConfigsClient interface {
+	Send(*XDSConfigResponse) error
+	Recv() (*XDSConfigRequest, error)
+	grpc.ClientStream
+}
+
+type globalKDSServiceStreamXDSConfigsClient struct {
+	grpc.ClientStream
+}
+
+func (x *globalKDSServiceStreamXDSConfigsClient) Send(m *XDSConfigResponse) error {
+	return x.ClientStream.SendMsg(m)
+}
+
+func (x *globalKDSServiceStreamXDSConfigsClient) Recv() (*XDSConfigRequest, error) {
+	m := new(XDSConfigRequest)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+// GlobalKDSServiceServer is the server API for GlobalKDSService service.
+type GlobalKDSServiceServer interface {
+	// StreamXDSConfigs is logically a service exposed by Zone CP so Global CP can
+	// execute Config Dumps. It is however represented by bi-directional streaming
+	// to leverage existing connection from Zone CP to Global CP.
+	StreamXDSConfigs(GlobalKDSService_StreamXDSConfigsServer) error
+}
+
+// UnimplementedGlobalKDSServiceServer can be embedded to have forward compatible implementations.
+type UnimplementedGlobalKDSServiceServer struct {
+}
+
+func (*UnimplementedGlobalKDSServiceServer) StreamXDSConfigs(GlobalKDSService_StreamXDSConfigsServer) error {
+	return status.Errorf(codes.Unimplemented, "method StreamXDSConfigs not implemented")
+}
+
+func RegisterGlobalKDSServiceServer(s *grpc.Server, srv GlobalKDSServiceServer) {
+	s.RegisterService(&_GlobalKDSService_serviceDesc, srv)
+}
+
+func _GlobalKDSService_StreamXDSConfigs_Handler(srv interface{}, stream grpc.ServerStream) error {
+	return srv.(GlobalKDSServiceServer).StreamXDSConfigs(&globalKDSServiceStreamXDSConfigsServer{stream})
+}
+
+type GlobalKDSService_StreamXDSConfigsServer interface {
+	Send(*XDSConfigRequest) error
+	Recv() (*XDSConfigResponse, error)
+	grpc.ServerStream
+}
+
+type globalKDSServiceStreamXDSConfigsServer struct {
+	grpc.ServerStream
+}
+
+func (x *globalKDSServiceStreamXDSConfigsServer) Send(m *XDSConfigRequest) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func (x *globalKDSServiceStreamXDSConfigsServer) Recv() (*XDSConfigResponse, error) {
+	m := new(XDSConfigResponse)
+	if err := x.ServerStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+var _GlobalKDSService_serviceDesc = grpc.ServiceDesc{
+	ServiceName: "kuma.mesh.v1alpha1.GlobalKDSService",
+	HandlerType: (*GlobalKDSServiceServer)(nil),
+	Methods:     []grpc.MethodDesc{},
+	Streams: []grpc.StreamDesc{
+		{
+			StreamName:    "StreamXDSConfigs",
+			Handler:       _GlobalKDSService_StreamXDSConfigs_Handler,
+			ServerStreams: true,
+			ClientStreams: true,
+		},
+	},
+	Metadata: "mesh/v1alpha1/kds.proto",
+}
diff --git a/api/mesh/v1alpha1/kds.proto b/api/mesh/v1alpha1/kds.proto
index 01349284f..a73b076ba 100644
--- a/api/mesh/v1alpha1/kds.proto
+++ b/api/mesh/v1alpha1/kds.proto
@@ -24,3 +24,41 @@ message KumaResource {
   Meta meta = 1;
   google.protobuf.Any spec = 2;
 }
+
+service GlobalKDSService {
+  // StreamXDSConfigs is logically a service exposed by Zone CP so Global CP can
+  // execute Config Dumps. It is however represented by bi-directional streaming
+  // to leverage existing connection from Zone CP to Global CP.
+  rpc StreamXDSConfigs(stream XDSConfigResponse)
+      returns (stream XDSConfigRequest);
+}
+
+// XDSConfigRequest is a request for XDS Config Dump that is executed on Zone
+// CP.
+message XDSConfigRequest {
+  // RequestID is a UUID of a request so we can correlate requests with response
+  // on one stream.
+  string request_id = 1;
+
+  // Type of resource (Dataplane, ZoneIngress, ZoneEgress)
+  string resource_type = 2;
+  // Name of the resource on which we execute config dump.
+  string resource_name = 3;
+  // Mesh of the resource on which we execute config dump. Should be empty for
+  // ZoneIngress, ZoneEgress.
+  string resource_mesh = 4;
+}
+
+// XDSConfigRequest is a response containing result of XDS Config Dump execution
+// on Zone CP.
+message XDSConfigResponse {
+  // RequestID is a UUID that was set by the Global CP.
+  string request_id = 1;
+
+  oneof result {
+    // Error that was captured by the Zone CP when executing XDS Config Dump.
+    string error = 2;
+    // The XDS Config that is a successful result of XDS Config DUMP execution.
+    bytes config = 3;
+  }
+}
diff --git a/app/kumactl/cmd/install/context/install_control_plane_context.go b/app/kumactl/cmd/install/context/install_control_plane_context.go
index 90b601c7d..ed5206cfe 100644
--- a/app/kumactl/cmd/install/context/install_control_plane_context.go
+++ b/app/kumactl/cmd/install/context/install_control_plane_context.go
@@ -101,7 +101,7 @@ func DefaultInstallCpContext() InstallCpContext {
 			Cni_conf_name:                           "kuma-cni.conf",
 			Cni_image_registry:                      "docker.io/kumahq",
 			Cni_image_repository:                    "install-cni",
-			Cni_image_tag:                           "0.0.9",
+			Cni_image_tag:                           "0.0.10",
 			ControlPlane_mode:                       core.Standalone,
 			ControlPlane_zone:                       "",
 			ControlPlane_globalZoneSyncService_type: "LoadBalancer",
diff --git a/deployments/charts/kuma/README.md b/deployments/charts/kuma/README.md
index 6b47c7821..176455319 100644
--- a/deployments/charts/kuma/README.md
+++ b/deployments/charts/kuma/README.md
@@ -73,7 +73,7 @@ A Helm chart for the Kuma Control Plane
 | cni.nodeSelector | object | `{"kubernetes.io/arch":"amd64","kubernetes.io/os":"linux"}` | Node Selector for the CNI pods |
 | cni.image.registry | string | `"docker.io"` | CNI image registry |
 | cni.image.repository | string | `"kumahq/install-cni"` | CNI image repository |
-| cni.image.tag | string | `"0.0.9"` | CNI image tag |
+| cni.image.tag | string | `"0.0.10"` | CNI image tag |
 | cni.podSecurityContext | object | `{}` | Security context at the pod level for cni |
 | cni.containerSecurityContext | object | `{}` | Security context at the container level for cni |
 | dataPlane.image.repository | string | `"kuma-dp"` | The Kuma DP image repository |
diff --git a/deployments/charts/kuma/values.yaml b/deployments/charts/kuma/values.yaml
index 4ad2b4f43..65b094f2f 100644
--- a/deployments/charts/kuma/values.yaml
+++ b/deployments/charts/kuma/values.yaml
@@ -242,7 +242,7 @@ cni:
     # -- CNI image repository
     repository: "kumahq/install-cni"
     # -- CNI image tag
-    tag: "0.0.9"
+    tag: "0.0.10"
 
   # -- Security context at the pod level for cni
   podSecurityContext: {}
diff --git a/docs/generated/cmd/kumactl/kumactl_install_control-plane.md b/docs/generated/cmd/kumactl/kumactl_install_control-plane.md
index 20a8354b9..7a348817d 100644
--- a/docs/generated/cmd/kumactl/kumactl_install_control-plane.md
+++ b/docs/generated/cmd/kumactl/kumactl_install_control-plane.md
@@ -22,7 +22,7 @@ kumactl install control-plane [flags]
       --cni-node-selector stringToString             node selector for CNI deployment (default [])
       --cni-registry string                          registry for the image of the Kuma CNI component (default "docker.io/kumahq")
       --cni-repository string                        repository for the image of the Kuma CNI component (default "install-cni")
-      --cni-version string                           version of the image of the Kuma CNI component (default "0.0.9")
+      --cni-version string                           version of the image of the Kuma CNI component (default "0.0.10")
       --control-plane-node-selector stringToString   node selector for Kuma Control Plane (default [])
       --control-plane-registry string                registry for the image of the Kuma Control Plane component (default "docker.io/kumahq")
       --control-plane-repository string              repository for the image of the Kuma Control Plane component (default "kuma-cp")
diff --git a/docs/madr/README.md b/docs/madr/README.md
new file mode 100644
index 000000000..983f972e8
--- /dev/null
+++ b/docs/madr/README.md
@@ -0,0 +1,16 @@
+# Markdown Architecture Decision Records
+
+This is mostly built on: https://github.com/adr/madr
+
+To start an MADR see: (001-adopt-madr.md)[001-adopt-madr.md].
+
+Accepted MADR:
+- [001-adopt-madr](001-adopt-madr.md) The proposal to describe how MADR will work. 
+
+Implemented MADR:
+
+None for the moment
+
+Deprecated MADR:
+
+None for the moment
diff --git a/docs/madr/decisions/000-template.md b/docs/madr/decisions/000-template.md
new file mode 100644
index 000000000..3d39dce9a
--- /dev/null
+++ b/docs/madr/decisions/000-template.md
@@ -0,0 +1,70 @@
+# {short title of solved problem and solution}
+
+* Status: {rejected | accepted | deprecated | superseded by [ADR-0005](0005-example.md) | implemented in `<link to releavant PR/issue...>`} <!-- recomended to have the status as accepted proactively and then to change it if needed -->
+
+Technical Story: {description | ticket/issue URL} <!-- link to the github issue -->
+
+## Context and Problem Statement
+
+{Describe the context and problem statement, e.g., in free form using two to three sentences. You may want to articulate the problem in form of a question.}
+
+## Decision Drivers <!-- optional -->
+
+* {driver 1, e.g., a force, facing concern, …}
+* {driver 2, e.g., a force, facing concern, …}
+* … <!-- numbers of drivers can vary -->
+
+## Considered Options
+
+* {option 1}
+* {option 2}
+* {option 3}
+* … <!-- numbers of options can vary -->
+
+## Decision Outcome
+
+Chosen option: "{option 1}", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | … | comes out best (see below)}.
+
+### Positive Consequences <!-- optional -->
+
+* {e.g., improvement of quality attribute satisfaction, follow-up decisions required, …}
+* …
+
+### Negative Consequences <!-- optional -->
+
+* {e.g., compromising quality attribute, follow-up decisions required, …}
+* …
+
+## Pros and Cons of the Options <!-- optional -->
+
+### {option 1}
+
+{example | description | pointer to more information | …} <!-- optional -->
+
+* Good, because {argument a}
+* Good, because {argument b}
+* Bad, because {argument c}
+* … <!-- numbers of pros and cons can vary -->
+
+### {option 2}
+
+{example | description | pointer to more information | …} <!-- optional -->
+
+* Good, because {argument a}
+* Good, because {argument b}
+* Bad, because {argument c}
+* … <!-- numbers of pros and cons can vary -->
+
+### {option 3}
+
+{example | description | pointer to more information | …} <!-- optional -->
+
+* Good, because {argument a}
+* Good, because {argument b}
+* Bad, because {argument c}
+* … <!-- numbers of pros and cons can vary -->
+
+## Links <!-- optional -->
+
+* {Link type} {Link to ADR} <!-- example: Refined by [ADR-0005](0005-example.md) -->
+* … <!-- numbers of links can vary -->
diff --git a/docs/madr/decisions/001-adopt-madr.md b/docs/madr/decisions/001-adopt-madr.md
new file mode 100644
index 000000000..fe5716ba3
--- /dev/null
+++ b/docs/madr/decisions/001-adopt-madr.md
@@ -0,0 +1,107 @@
+# Adopt MADR 
+
+* Status: accepted 
+
+Technical Story: N/A 
+
+## Context and Problem Statement
+
+With the growth of Kuma it has now become necessary to establish a better process for designing important changes.
+So far we've been using [proposals](/docs/proposals/README.md) but they lack of a good format.
+
+## Decision Drivers
+
+* Keep an historical record of decisions, what motivated them and what alternatives were taken in consideration 
+* Have a standard way to describe new designs and facilitate reviews and discussion. 
+* Help users to provide input to a change and how it might impact their existing setup. 
+
+## Considered Options
+
+* [MADR](https://github.com/adr/madr) 
+* [KEPs](https://github.com/kubernetes/enhancements/tree/master/keps) 
+* Github issues
+
+## Decision Outcome
+
+Chosen option: MADR, because of familiarity and simplicity. 
+
+### Process
+
+#### When to open a MADR
+
+- if a ticket is marked as `kind/design` during triage it means that it was decided a design document was required.
+- if anything touches the public api it should have a document as well. This includes:
+  - Kuma api
+  - Kubernetes CRDs
+  - Metrics
+  - Upgrade path
+  - kumactl args and options
+
+> You should be opening a MADR if you are not thinking of shortly working on implementing it.
+> If you have an idea but no time to implement it open an issue instead.
+
+#### Open a new MADR
+
+- Always use the [template](/docs/madr/decisions/000-template.md) and pick the next number.
+- Update the [README](/docs/madr/README.md) with a link to the MADR and a 1 line description of the subject.
+- Socialize your MADR in Kuma Slack.
+
+> Prior to open a MADR it might be useful to have an informal chat with committers as they may have suggestions/ideas.
+
+#### Process to reach consensus on a MADR
+
+The goal of a MADR is to get to a consensus to either accept or reject a design.
+The person that opens the MADR has the responsibility to socialize and gather consensus.
+They may need to call a meeting once it's ready to iron out the final points.
+
+#### When is a MADR accepted
+
+MADR must receive at least 3 approvals and no veto to be accepted.
+
+Only votes from committers do count.
+Pull Request reviews are used to submit approvals or vetos.
+
+Once a MADR is accepted it is merged in the repo.
+
+The MADR has one of the following status: `rejected | accepted | deprecated | superseded by [ADR-0005](0005-example.md) | implemented in ....`
+
+It's recommended to open the PR with the status `accepted` as it's very likely what you are trying to get to.
+The status can be updated once it's merged depending on the evolution of Kuma.
+Here's a description of when to use each status
+
+- rejected: If after a discussion we decided to go against a design or a decision it's still good to keep a trace, we'll therefore merge it .
+- accepted: This has been reviewed and accepted, but it's not implemented yet, or it's just a process change.
+- deprecated: We did it but the feature was removed. It's important to add information about release that will either remove the feature or point to the PR and version that's removing it.
+- superseded: There's anoter MADR that seems to be a better approach to this problem at stake.
+- implemented in: The PR/Issue and Kuma version this feature was added. If this is a new process some information on the first times this is done.
+
+#### What happens when a MADR is accepted
+
+When a MADR is merged if there was a design issue, implementation issues should be created and mentioned in the design issue.
+
+### Positive Consequences
+
+* We'll track rejected options 
+* There will be a single place to have an overview of what's being developed.
+
+### Negative Consequences
+
+* Submitting changes will be slower.
+
+## Pros and Cons of the Options
+
+### KEP 
+
+While the KEP process is very complete it's pretty heavy.
+It seems that for the moment it's simpler to use a less complete process.
+
+### Github issues 
+
+Github issues are very lightweight. 
+
+- However, it's harder to find what is a design issue and what is an implementation issue.
+- Long exchanges are not threaded.
+- It's hard to decide what's the state of a design from issues.
+
+While it's possible to use labels for a lot of these.
+It seems that using files in a VCS is a little more straightforward for discovery and persistence.
diff --git a/docs/proposals/README.md b/docs/proposals/README.md
new file mode 100644
index 000000000..9f7ad569c
--- /dev/null
+++ b/docs/proposals/README.md
@@ -0,0 +1,3 @@
+# Proposals
+
+These are old proposals we've now adopted the [MADR format](/docs/madr/README.md)
diff --git a/go.mod b/go.mod
index 145114550..ba03566b4 100644
--- a/go.mod
+++ b/go.mod
@@ -25,14 +25,14 @@ require (
 	github.com/kelseyhightower/envconfig v1.4.0
 	github.com/kumahq/kuma-net v0.1.3
 	github.com/kumahq/protoc-gen-kumadoc v0.2.0
-	github.com/lib/pq v1.10.5
-	github.com/miekg/dns v1.1.48
+	github.com/lib/pq v1.10.6
+	github.com/miekg/dns v1.1.49
 	github.com/onsi/ginkgo/v2 v2.1.4
 	github.com/onsi/gomega v1.19.0
 	github.com/operator-framework/operator-lib v0.10.0
 	github.com/patrickmn/go-cache v2.1.0+incompatible
 	github.com/pkg/errors v0.9.1
-	github.com/prometheus/client_golang v1.12.1
+	github.com/prometheus/client_golang v1.12.2
 	github.com/prometheus/client_model v0.2.0
 	github.com/prometheus/common v0.34.0
 	github.com/prometheus/prometheus v0.0.0-00010101000000-000000000000
diff --git a/go.sum b/go.sum
index 54c7f8248..adc09768a 100644
--- a/go.sum
+++ b/go.sum
@@ -984,8 +984,8 @@ github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ=
-github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
+github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
 github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
 github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
@@ -1050,8 +1050,8 @@ github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88J
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
 github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
-github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ=
-github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
+github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8=
+github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
 github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
 github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
 github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
@@ -1250,8 +1250,9 @@ github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQ
 github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
 github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
+github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
+github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
 github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
diff --git a/mk/check.mk b/mk/check.mk
index b5d563c4c..b6d8a9561 100644
--- a/mk/check.mk
+++ b/mk/check.mk
@@ -41,6 +41,10 @@ ginkgo/unfocus:
 .PHONY: format
 format: fmt generate docs tidy ginkgo/unfocus
 
+.PHONY: kube-lint
+kube-lint:
+	$(GOPATH_BIN_DIR)/kube-linter lint .
+
 .PHONY: check
-check: format helm-lint golangci-lint shellcheck ## Dev: Run code checks (go fmt, go vet, ...)
+check: format helm-lint golangci-lint shellcheck kube-lint ## Dev: Run code checks (go fmt, go vet, ...)
 	git diff --quiet || test $$(git diff --name-only | grep -v -e 'go.mod$$' -e 'go.sum$$' | wc -l) -eq 0 || ( echo "The following changes (result of code generators and code checks) have been detected:" && git --no-pager diff && false ) # fail if Git working tree is dirty
diff --git a/mk/dev.mk b/mk/dev.mk
index 0d23f25df..9ce26e3b3 100644
--- a/mk/dev.mk
+++ b/mk/dev.mk
@@ -13,6 +13,7 @@ SHELLCHECK_VERSION := v0.8.0
 YQ_VERSION := v4.24.2
 ETCD_VERSION := v3.5.3
 HELM_VERSION := v3.8.2
+KUBE_LINTER_VERSION := v0.0.0-20220513142942-846f273ed465
 
 CI_KUBEBUILDER_VERSION ?= 2.3.2
 CI_KUBECTL_VERSION ?= v1.23.5
@@ -101,7 +102,8 @@ dev/tools/all: dev/install/protoc dev/install/protobuf-wellknown-types \
 	dev/install/helm-docs \
 	dev/install/data-plane-api \
 	dev/install/shellcheck \
-	dev/install/yq
+	dev/install/yq \
+	dev/install/kube-lint
 
 .PHONY: dev/install/protoc-gen-kumadoc
 dev/install/protoc-gen-kumadoc:
@@ -310,6 +312,10 @@ dev/install/helm-docs: ## Bootstrap: Install helm-docs
 		&& set +x \
 		&& echo "helm-docs $(HELM_DOCS_VERSION) has been installed at $(HELM_DOCS_PATH)" ; fi
 
+.PHONY: dev/install/kube-lint
+dev/install/kube-lint:
+	go install golang.stackrox.io/kube-linter/cmd/kube-linter@$(KUBE_LINTER_VERSION)
+
 $(KUBECONFIG_DIR):
 	@mkdir -p $(KUBECONFIG_DIR)
 
diff --git a/mk/envoy.mk b/mk/envoy.mk
index 2a55fb85e..a86ae41ad 100644
--- a/mk/envoy.mk
+++ b/mk/envoy.mk
@@ -2,7 +2,7 @@ KUMA_DIR ?= .
 
 BUILD_ENVOY_FROM_SOURCES ?= false
 
-ENVOY_TAG ?= v1.21.1 # commit hash or git tag
+ENVOY_TAG ?= v1.22.0 # commit hash or git tag
 # Remember to update pkg/version/compatibility.go
 ENVOY_VERSION = $(shell ${KUMA_DIR}/tools/envoy/version.sh ${ENVOY_TAG})
 
diff --git a/pkg/api-server/inspect_endpoints.go b/pkg/api-server/inspect_endpoints.go
index dcc8bf4ec..c3fccd2ea 100644
--- a/pkg/api-server/inspect_endpoints.go
+++ b/pkg/api-server/inspect_endpoints.go
@@ -2,14 +2,13 @@ package api_server
 
 import (
 	"fmt"
-	"net/http"
 	"sort"
 
 	"github.com/emicklei/go-restful"
 
 	api_server_types "github.com/kumahq/kuma/pkg/api-server/types"
 	kuma_cp "github.com/kumahq/kuma/pkg/config/app/kuma-cp"
-	config_core "github.com/kumahq/kuma/pkg/config/core"
+	"github.com/kumahq/kuma/pkg/config/core"
 	"github.com/kumahq/kuma/pkg/core/policy"
 	core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
 	"github.com/kumahq/kuma/pkg/core/resources/manager"
@@ -18,7 +17,6 @@ import (
 	"github.com/kumahq/kuma/pkg/core/resources/registry"
 	"github.com/kumahq/kuma/pkg/core/resources/store"
 	rest_errors "github.com/kumahq/kuma/pkg/core/rest/errors"
-	"github.com/kumahq/kuma/pkg/core/rest/errors/types"
 	"github.com/kumahq/kuma/pkg/core/user"
 	"github.com/kumahq/kuma/pkg/core/validators"
 	core_xds "github.com/kumahq/kuma/pkg/core/xds"
@@ -83,42 +81,22 @@ func addInspectEndpoints(
 			Returns(200, "OK", nil),
 	)
 
-	if cfg.Mode != config_core.Global {
-		ws.Route(
-			ws.GET("/meshes/{mesh}/dataplanes/{dataplane}/xds").To(inspectDataplaneXDS(envoyAdminClient, configDumpAccess, rm, cfg.GetEnvoyAdminPort())).
-				Doc("inspect dataplane XDS configuration").
-				Param(ws.PathParameter("mesh", "mesh name").DataType("string")).
-				Param(ws.PathParameter("dataplane", "dataplane name").DataType("string")),
-		)
-		ws.Route(
-			ws.GET("/zoneingresses/{zoneingress}/xds").To(inspectZoneIngressXDS(envoyAdminClient, configDumpAccess, rm, cfg.Multizone.Zone.Name, cfg.GetEnvoyAdminPort())).
-				Doc("inspect zone ingresses XDS configuration").
-				Param(ws.PathParameter("zoneingress", "zoneingress name").DataType("string")),
-		)
-		ws.Route(
-			ws.GET("/zoneegresses/{zoneegress}/xds").To(inspectZoneEgressXDS(envoyAdminClient, configDumpAccess, rm, cfg.GetEnvoyAdminPort())).
-				Doc("inspect zone egresses XDS configuration").
-				Param(ws.PathParameter("zoneegress", "zoneegress name").DataType("string")),
-		)
-	} else {
-		methodNotAllowed := func(_ *restful.Request, response *restful.Response) {
-			kumaErr := types.Error{
-				Title:   "Method is not allowed",
-				Details: "It it not possible to inspect envoy config dump on Global CP. Please consider using Zone CP of the corresponding zone",
-			}
-			rest_errors.WriteError(response, http.StatusMethodNotAllowed, kumaErr)
-		}
-		ws.Route(
-			ws.GET("/meshes/{mesh}/dataplanes/{dataplane}/xds").To(methodNotAllowed).
-				Param(ws.PathParameter("mesh", "mesh name").DataType("string")).
-				Param(ws.PathParameter("dataplane", "dataplane name").DataType("string")))
-		ws.Route(
-			ws.GET("/zoneingresses/{zoneingress}/xds").To(methodNotAllowed).
-				Param(ws.PathParameter("zoneingress", "zoneingress name").DataType("string")))
-		ws.Route(
-			ws.GET("/zoneegresses/{zoneegress}/xds").To(methodNotAllowed).
-				Param(ws.PathParameter("zoneegress", "zoneegress name").DataType("string")))
-	}
+	ws.Route(
+		ws.GET("/meshes/{mesh}/dataplanes/{dataplane}/xds").To(inspectDataplaneXDS(envoyAdminClient, configDumpAccess, rm, cfg.GetEnvoyAdminPort())).
+			Doc("inspect dataplane XDS configuration").
+			Param(ws.PathParameter("mesh", "mesh name").DataType("string")).
+			Param(ws.PathParameter("dataplane", "dataplane name").DataType("string")),
+	)
+	ws.Route(
+		ws.GET("/zoneingresses/{zoneingress}/xds").To(inspectZoneIngressXDS(cfg.Mode, envoyAdminClient, configDumpAccess, rm, cfg.Multizone.Zone.Name, cfg.GetEnvoyAdminPort())).
+			Doc("inspect zone ingresses XDS configuration").
+			Param(ws.PathParameter("zoneingress", "zoneingress name").DataType("string")),
+	)
+	ws.Route(
+		ws.GET("/zoneegresses/{zoneegress}/xds").To(inspectZoneEgressXDS(envoyAdminClient, configDumpAccess, rm, cfg.GetEnvoyAdminPort())).
+			Doc("inspect zone egresses XDS configuration").
+			Param(ws.PathParameter("zoneegress", "zoneegress name").DataType("string")),
+	)
 
 	for _, desc := range registry.Global().ObjectDescriptors(core_model.AllowedToInspect()) {
 		ws.Route(
@@ -376,7 +354,7 @@ func inspectDataplaneXDS(
 			return
 		}
 
-		configDump, err := envoyAdminClient.ConfigDump(dp, defaultAdminPort)
+		configDump, err := envoyAdminClient.ConfigDump(ctx, dp)
 		if err != nil {
 			rest_errors.HandleError(response, err, "Could not get config_dump")
 			return
@@ -390,6 +368,7 @@ func inspectDataplaneXDS(
 }
 
 func inspectZoneIngressXDS(
+	mode core.CpMode,
 	envoyAdminClient admin.EnvoyAdminClient,
 	access access.ConfigDumpAccess,
 	rm manager.ResourceManager,
@@ -411,13 +390,13 @@ func inspectZoneIngressXDS(
 			return
 		}
 
-		if zi.IsRemoteIngress(localZone) {
+		if mode == core.Zone && zi.IsRemoteIngress(localZone) {
 			rest_errors.HandleError(response, &validators.ValidationError{},
 				"Could not connect to zone ingress that resides in another zone")
 			return
 		}
 
-		configDump, err := envoyAdminClient.ConfigDump(zi, defaultAdminPort)
+		configDump, err := envoyAdminClient.ConfigDump(ctx, zi)
 		if err != nil {
 			rest_errors.HandleError(response, err, "Could not get config_dump")
 			return
@@ -451,7 +430,7 @@ func inspectZoneEgressXDS(
 			return
 		}
 
-		configDump, err := envoyAdminClient.ConfigDump(ze, defaultAdminPort)
+		configDump, err := envoyAdminClient.ConfigDump(ctx, ze)
 		if err != nil {
 			rest_errors.HandleError(response, err, "Could not get config_dump")
 			return
diff --git a/pkg/core/bootstrap/bootstrap.go b/pkg/core/bootstrap/bootstrap.go
index bf7c7e67b..68048addd 100644
--- a/pkg/core/bootstrap/bootstrap.go
+++ b/pkg/core/bootstrap/bootstrap.go
@@ -102,17 +102,6 @@ func buildRuntime(appCtx context.Context, cfg kuma_cp.Config) (core_runtime.Runt
 	builder.WithLeaderInfo(leaderInfoComponent)
 
 	builder.WithLookupIP(lookup.CachedLookupIP(net.LookupIP, cfg.General.DNSCacheTTL))
-	envoyAdminClient, err := admin.NewEnvoyAdminClient(
-		builder.ResourceManager(),
-		builder.CaManagers(),
-		builder.Config().DpServer.TlsCertFile,
-		builder.Config().DpServer.TlsKeyFile,
-		builder.Config().GetEnvoyAdminPort(),
-	)
-	if err != nil {
-		return nil, err
-	}
-	builder.WithEnvoyAdminClient(envoyAdminClient)
 	builder.WithAPIManager(customization.NewAPIList())
 	builder.WithXDSHooks(&xds_hooks.Hooks{})
 	caProvider, err := secrets.NewCaProvider(builder.CaManagers(), builder.Metrics())
@@ -123,6 +112,22 @@ func buildRuntime(appCtx context.Context, cfg kuma_cp.Config) (core_runtime.Runt
 	builder.WithDpServer(server.NewDpServer(*cfg.DpServer, builder.Metrics()))
 	builder.WithKDSContext(kds_context.DefaultContext(builder.ResourceManager(), cfg.Multizone.Zone.Name))
 
+	if cfg.Mode == config_core.Global {
+		builder.WithEnvoyAdminClient(admin.NewKDSEnvoyAdminClient(builder.KDSContext().XdsConfigStreams))
+	} else {
+		envoyAdminClient, err := admin.NewEnvoyAdminClient(
+			builder.ResourceManager(),
+			builder.CaManagers(),
+			builder.Config().DpServer.TlsCertFile,
+			builder.Config().DpServer.TlsKeyFile,
+			builder.Config().GetEnvoyAdminPort(),
+		)
+		if err != nil {
+			return nil, err
+		}
+		builder.WithEnvoyAdminClient(envoyAdminClient)
+	}
+
 	builder.WithAccess(core_runtime.Access{
 		ResourceAccess:       resources_access.NewAdminResourceAccess(builder.Config().Access.Static.AdminResources),
 		DataplaneTokenAccess: tokens_access.NewStaticGenerateDataplaneTokenAccess(builder.Config().Access.Static.GenerateDPToken),
diff --git a/pkg/core/resources/model/resource.go b/pkg/core/resources/model/resource.go
index ec28ab112..60fb3aea3 100644
--- a/pkg/core/resources/model/resource.go
+++ b/pkg/core/resources/model/resource.go
@@ -262,3 +262,8 @@ func (p *Pagination) SetNextOffset(nextOffset string) {
 func ErrorInvalidItemType(expected, actual interface{}) error {
 	return fmt.Errorf("Invalid argument type: expected=%q got=%q", reflect.TypeOf(expected), reflect.TypeOf(actual))
 }
+
+type ResourceWithAddress interface {
+	Resource
+	AdminAddress(defaultAdminPort uint32) string
+}
diff --git a/pkg/envoy/admin/client.go b/pkg/envoy/admin/client.go
index a2da752da..f3e0508c0 100644
--- a/pkg/envoy/admin/client.go
+++ b/pkg/envoy/admin/client.go
@@ -25,14 +25,9 @@ import (
 	xds_tls "github.com/kumahq/kuma/pkg/xds/envoy/tls"
 )
 
-type ResourceWithAddress interface {
-	core_model.Resource
-	AdminAddress(defaultAdminPort uint32) string
-}
-
 type EnvoyAdminClient interface {
-	PostQuit(dataplane *core_mesh.DataplaneResource) error
-	ConfigDump(proxy ResourceWithAddress, defaultAdminPort uint32) ([]byte, error)
+	PostQuit(ctx context.Context, dataplane *core_mesh.DataplaneResource) error
+	ConfigDump(ctx context.Context, proxy core_model.ResourceWithAddress) ([]byte, error)
 }
 
 type envoyAdminClient struct {
@@ -146,14 +141,14 @@ const (
 	quitquitquit = "quitquitquit"
 )
 
-func (a *envoyAdminClient) PostQuit(dataplane *core_mesh.DataplaneResource) error {
+func (a *envoyAdminClient) PostQuit(ctx context.Context, dataplane *core_mesh.DataplaneResource) error {
 	httpClient, err := a.buildHTTPClient(dataplane.Meta.GetMesh(), dataplane.Spec.GetIdentifyingService())
 	if err != nil {
 		return err
 	}
 
 	url := fmt.Sprintf("https://%s/%s", dataplane.AdminAddress(a.defaultAdminPort), quitquitquit)
-	request, err := http.NewRequest("POST", url, nil)
+	request, err := http.NewRequestWithContext(ctx, "POST", url, nil)
 	if err != nil {
 		return err
 	}
@@ -175,7 +170,7 @@ func (a *envoyAdminClient) PostQuit(dataplane *core_mesh.DataplaneResource) erro
 	return nil
 }
 
-func (a *envoyAdminClient) ConfigDump(proxy ResourceWithAddress, defaultAdminAddress uint32) ([]byte, error) {
+func (a *envoyAdminClient) ConfigDump(ctx context.Context, proxy core_model.ResourceWithAddress) ([]byte, error) {
 	var httpClient *http.Client
 	var err error
 	u := &url.URL{}
@@ -197,16 +192,16 @@ func (a *envoyAdminClient) ConfigDump(proxy ResourceWithAddress, defaultAdminAdd
 		return nil, errors.New("unsupported proxy type")
 	}
 
-	if host, _, err := net.SplitHostPort(proxy.AdminAddress(defaultAdminAddress)); err == nil && host == "127.0.0.1" {
+	if host, _, err := net.SplitHostPort(proxy.AdminAddress(a.defaultAdminPort)); err == nil && host == "127.0.0.1" {
 		httpClient = &http.Client{
 			Timeout: 5 * time.Second,
 		}
 		u.Scheme = "http"
 	}
 
-	u.Host = proxy.AdminAddress(defaultAdminAddress)
+	u.Host = proxy.AdminAddress(a.defaultAdminPort)
 	u.Path = "config_dump"
-	request, err := http.NewRequest("GET", u.String(), nil)
+	request, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/envoy/admin/kds_client.go b/pkg/envoy/admin/kds_client.go
new file mode 100644
index 000000000..02f24cf59
--- /dev/null
+++ b/pkg/envoy/admin/kds_client.go
@@ -0,0 +1,73 @@
+package admin
+
+import (
+	"context"
+	"strings"
+
+	"github.com/pkg/errors"
+
+	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
+	"github.com/kumahq/kuma/pkg/core"
+	core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
+	core_model "github.com/kumahq/kuma/pkg/core/resources/model"
+	"github.com/kumahq/kuma/pkg/kds/service"
+)
+
+type kdsEnvoyAdminClient struct {
+	streams service.XDSConfigStreams
+}
+
+func NewKDSEnvoyAdminClient(streams service.XDSConfigStreams) EnvoyAdminClient {
+	return &kdsEnvoyAdminClient{
+		streams: streams,
+	}
+}
+
+var _ EnvoyAdminClient = &kdsEnvoyAdminClient{}
+
+func (k *kdsEnvoyAdminClient) PostQuit(context.Context, *core_mesh.DataplaneResource) error {
+	panic("not implemented")
+}
+
+func (k *kdsEnvoyAdminClient) ConfigDump(ctx context.Context, proxy core_model.ResourceWithAddress) ([]byte, error) {
+	zone, nameInZone, err := resNameInZone(proxy.GetMeta().GetName())
+	if err != nil {
+		return nil, err
+	}
+	reqId := core.NewUUID()
+	err = k.streams.Send(zone, &mesh_proto.XDSConfigRequest{
+		RequestId:    reqId,
+		ResourceType: string(proxy.Descriptor().Name),
+		ResourceName: nameInZone,                // send the name which without the added prefix
+		ResourceMesh: proxy.GetMeta().GetMesh(), // should be empty for ZoneIngress/ZoneEgress
+	})
+	if err != nil {
+		return nil, errors.Wrapf(err, "could not send XDSConfigRequest")
+	}
+
+	defer k.streams.DeleteWatch(zone, reqId)
+	ch := make(chan *mesh_proto.XDSConfigResponse)
+	if err := k.streams.WatchResponse(zone, reqId, ch); err != nil {
+		return nil, errors.Wrapf(err, "could not watch the response")
+	}
+
+	select {
+	case <-ctx.Done():
+		return nil, errors.New("timeout")
+	case resp := <-ch:
+		if resp.GetError() != "" {
+			return nil, errors.Errorf("error response from Zone CP: %s", resp.GetError())
+		}
+		return resp.GetConfig(), nil
+	}
+}
+
+func resNameInZone(nameInGlobal string) (string, string, error) {
+	parts := strings.Split(nameInGlobal, ".")
+	if len(parts) < 2 {
+		return "", "", errors.New("wrong name format. Expected {zone}.{name}")
+	}
+	zone := parts[0] // zone is added by Global CP as a prefix for Dataplane/ZoneIngress/ZoneEgress
+	nameInZone := strings.Join(parts[1:], ".")
+	return zone, nameInZone, nil
+}
diff --git a/pkg/kds/context/context.go b/pkg/kds/context/context.go
index 03e43393b..0b600bd2b 100644
--- a/pkg/kds/context/context.go
+++ b/pkg/kds/context/context.go
@@ -22,6 +22,7 @@ import (
 	"github.com/kumahq/kuma/pkg/kds"
 	"github.com/kumahq/kuma/pkg/kds/mux"
 	"github.com/kumahq/kuma/pkg/kds/reconcile"
+	"github.com/kumahq/kuma/pkg/kds/service"
 	"github.com/kumahq/kuma/pkg/kds/util"
 	zone_tokens "github.com/kumahq/kuma/pkg/tokens/builtin/zone"
 	"github.com/kumahq/kuma/pkg/tokens/builtin/zoneingress"
@@ -40,6 +41,8 @@ type Context struct {
 
 	GlobalResourceMapper reconcile.ResourceMapper
 	ZoneResourceMapper   reconcile.ResourceMapper
+
+	XdsConfigStreams service.XDSConfigStreams
 }
 
 func DefaultContext(manager manager.ResourceManager, zone string) *Context {
@@ -56,6 +59,7 @@ func DefaultContext(manager manager.ResourceManager, zone string) *Context {
 		Configs:              configs,
 		GlobalResourceMapper: MapZoneTokenSigningKeyGlobalToPublicKey(ctx, manager),
 		ZoneResourceMapper:   MapInsightResourcesZeroGeneration,
+		XdsConfigStreams:     service.NewXdsConfigStreams(),
 	}
 }
 
diff --git a/pkg/kds/global/components.go b/pkg/kds/global/components.go
index 8da6644f6..459025732 100644
--- a/pkg/kds/global/components.go
+++ b/pkg/kds/global/components.go
@@ -21,6 +21,7 @@ import (
 	"github.com/kumahq/kuma/pkg/kds/client"
 	"github.com/kumahq/kuma/pkg/kds/mux"
 	kds_server "github.com/kumahq/kuma/pkg/kds/server"
+	"github.com/kumahq/kuma/pkg/kds/service"
 	sync_store "github.com/kumahq/kuma/pkg/kds/store"
 	"github.com/kumahq/kuma/pkg/kds/util"
 	resources_k8s "github.com/kumahq/kuma/pkg/plugins/resources/k8s"
@@ -71,7 +72,13 @@ func Setup(rt runtime.Runtime) (err error) {
 		}()
 		return nil
 	})
-	return rt.Add(mux.NewServer(onSessionStarted, rt.KDSContext().GlobalServerFilters, *rt.Config().Multizone.Global.KDS, rt.Metrics()))
+	return rt.Add(mux.NewServer(
+		onSessionStarted,
+		rt.KDSContext().GlobalServerFilters,
+		*rt.Config().Multizone.Global.KDS,
+		rt.Metrics(),
+		service.NewGlobalKDSServiceServer(rt.KDSContext().XdsConfigStreams),
+	))
 }
 
 func createZoneIfAbsent(name string, resManager manager.ResourceManager) error {
diff --git a/pkg/kds/mux/client.go b/pkg/kds/mux/client.go
index 7b4b16613..8c03bf384 100644
--- a/pkg/kds/mux/client.go
+++ b/pkg/kds/mux/client.go
@@ -7,17 +7,21 @@ import (
 	"net/url"
 	"os"
 
+	"github.com/go-logr/logr"
 	"github.com/pkg/errors"
 	"google.golang.org/grpc"
+	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/keepalive"
 	"google.golang.org/grpc/metadata"
+	"google.golang.org/grpc/status"
 
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
 	"github.com/kumahq/kuma/pkg/config/multizone"
 	"github.com/kumahq/kuma/pkg/core"
 	"github.com/kumahq/kuma/pkg/core/runtime/component"
+	"github.com/kumahq/kuma/pkg/kds/service"
 	"github.com/kumahq/kuma/pkg/metrics"
 )
 
@@ -26,22 +30,32 @@ var (
 )
 
 type client struct {
-	callbacks Callbacks
-	globalURL string
-	clientID  string
-	config    multizone.KdsClientConfig
-	metrics   metrics.Metrics
-	ctx       context.Context
+	callbacks          Callbacks
+	globalURL          string
+	clientID           string
+	config             multizone.KdsClientConfig
+	metrics            metrics.Metrics
+	ctx                context.Context
+	xdsConfigProcessor service.XDSConfigProcessor
 }
 
-func NewClient(globalURL string, clientID string, callbacks Callbacks, config multizone.KdsClientConfig, metrics metrics.Metrics, ctx context.Context) component.Component {
+func NewClient(
+	ctx context.Context,
+	globalURL string,
+	clientID string,
+	callbacks Callbacks,
+	config multizone.KdsClientConfig,
+	metrics metrics.Metrics,
+	xdsConfigProcessor service.XDSConfigProcessor,
+) component.Component {
 	return &client{
-		callbacks: callbacks,
-		globalURL: globalURL,
-		clientID:  clientID,
-		config:    config,
-		metrics:   metrics,
-		ctx:       ctx,
+		ctx:                ctx,
+		callbacks:          callbacks,
+		globalURL:          globalURL,
+		clientID:           clientID,
+		config:             config,
+		metrics:            metrics,
+		xdsConfigProcessor: xdsConfigProcessor,
 	}
 }
 
@@ -81,7 +95,6 @@ func (c *client) Start(stop <-chan struct{}) (errs error) {
 			errs = errors.Wrapf(err, "failed to close a connection")
 		}
 	}()
-	muxClient := mesh_proto.NewMultiplexServiceClient(conn)
 
 	withKDSCtx, cancel := context.WithCancel(metadata.AppendToOutgoingContext(c.ctx,
 		"client-id", c.clientID,
@@ -90,15 +103,32 @@ func (c *client) Start(stop <-chan struct{}) (errs error) {
 	defer cancel()
 
 	log := muxClientLog.WithValues("client-id", c.clientID)
+	errorCh := make(chan error)
+	go c.startKDSMultiplex(withKDSCtx, log, conn, stop, errorCh)
+	go c.startXDSConfigs(withKDSCtx, log, conn, stop, errorCh)
+
+	select {
+	case <-stop:
+		cancel()
+		return
+	case err = <-errorCh:
+		return err
+	}
+}
+
+func (c *client) startKDSMultiplex(ctx context.Context, log logr.Logger, conn *grpc.ClientConn, stop <-chan struct{}, errorCh chan error) {
+	muxClient := mesh_proto.NewMultiplexServiceClient(conn)
 	log.Info("initializing Kuma Discovery Service (KDS) stream for global-zone sync of resources")
-	stream, err := muxClient.StreamMessage(withKDSCtx)
+	stream, err := muxClient.StreamMessage(ctx)
 	if err != nil {
-		return err
+		errorCh <- err
+		return
 	}
 	session := NewSession("global", stream)
 	if err := c.callbacks.OnSessionStarted(session); err != nil {
 		log.Error(err, "closing KDS stream after callback error")
-		return err
+		errorCh <- err
+		return
 	}
 	select {
 	case <-stop:
@@ -106,15 +136,49 @@ func (c *client) Start(stop <-chan struct{}) (errs error) {
 		if err := stream.CloseSend(); err != nil {
 			log.Error(err, "CloseSend returned an error")
 		}
-		cancel() // In this case we cancel the context early
 		err = <-session.Error()
+		errorCh <- err
 	case err = <-session.Error():
 		log.Error(err, "KDS stream failed prematurely, will restart in background")
 		if err := stream.CloseSend(); err != nil {
 			log.Error(err, "CloseSend returned an error")
 		}
+		errorCh <- err
+		return
+	}
+}
+
+func (c *client) startXDSConfigs(ctx context.Context, log logr.Logger, conn *grpc.ClientConn, stop <-chan struct{}, errorCh chan error) {
+	muxClient := mesh_proto.NewGlobalKDSServiceClient(conn)
+	log.Info("initializing XDS Configs stream for executing config dump on data plane proxies")
+	stream, err := muxClient.StreamXDSConfigs(ctx)
+	if err != nil {
+		errorCh <- err
+		return
+	}
+
+	processingErrorsCh := make(chan error)
+	go c.xdsConfigProcessor.StartProcessing(stream, processingErrorsCh)
+
+	select {
+	case <-stop:
+		log.Info("XDSConfigDumps stream stopped", "reason", err)
+		if err := stream.CloseSend(); err != nil {
+			log.Error(err, "CloseSend returned an error")
+		}
+	case err = <-processingErrorsCh:
+		if status.Code(err) == codes.Unimplemented {
+			log.Error(err, "XDSConfigDumps stream failed, because Global CP does not implement XDS Config. Upgrade Global CP.")
+			// backwards compatibility. Do not rethrow error, so KDS multiplex can still operate.
+			return
+		}
+		log.Error(err, "XDSConfigDumps stream failed prematurely, will restart in background")
+		if err := stream.CloseSend(); err != nil {
+			log.Error(err, "CloseSend returned an error")
+		}
+		errorCh <- err
+		return
 	}
-	return err
 }
 
 func (c *client) NeedLeaderElection() bool {
diff --git a/pkg/kds/mux/server.go b/pkg/kds/mux/server.go
index 716b1b3d5..a764b146d 100644
--- a/pkg/kds/mux/server.go
+++ b/pkg/kds/mux/server.go
@@ -9,12 +9,13 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/keepalive"
-	"google.golang.org/grpc/metadata"
 
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
 	"github.com/kumahq/kuma/pkg/config/multizone"
 	"github.com/kumahq/kuma/pkg/core"
 	"github.com/kumahq/kuma/pkg/core/runtime/component"
+	"github.com/kumahq/kuma/pkg/kds/service"
+	"github.com/kumahq/kuma/pkg/kds/util"
 	core_metrics "github.com/kumahq/kuma/pkg/metrics"
 )
 
@@ -41,22 +42,30 @@ func (f OnSessionStartedFunc) OnSessionStarted(session Session) error {
 }
 
 type server struct {
-	config    multizone.KdsServerConfig
-	callbacks Callbacks
-	filters   []Filter
-	metrics   core_metrics.Metrics
+	config        multizone.KdsServerConfig
+	callbacks     Callbacks
+	filters       []Filter
+	metrics       core_metrics.Metrics
+	serviceServer *service.GlobalKDSServiceServer
 }
 
 var (
 	_ component.Component = &server{}
 )
 
-func NewServer(callbacks Callbacks, filters []Filter, config multizone.KdsServerConfig, metrics core_metrics.Metrics) component.Component {
+func NewServer(
+	callbacks Callbacks,
+	filters []Filter,
+	config multizone.KdsServerConfig,
+	metrics core_metrics.Metrics,
+	serviceServer *service.GlobalKDSServiceServer,
+) component.Component {
 	return &server{
-		callbacks: callbacks,
-		filters:   filters,
-		config:    config,
-		metrics:   metrics,
+		callbacks:     callbacks,
+		filters:       filters,
+		config:        config,
+		metrics:       metrics,
+		serviceServer: serviceServer,
 	}
 }
 
@@ -87,6 +96,7 @@ func (s *server) Start(stop <-chan struct{}) error {
 
 	// register services
 	mesh_proto.RegisterMultiplexServiceServer(grpcServer, s)
+	mesh_proto.RegisterGlobalKDSServiceServer(grpcServer, s.serviceServer)
 	s.metrics.RegisterGRPC(grpcServer)
 
 	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", s.config.GrpcPort))
@@ -117,14 +127,10 @@ func (s *server) Start(stop <-chan struct{}) error {
 }
 
 func (s *server) StreamMessage(stream mesh_proto.MultiplexService_StreamMessageServer) error {
-	md, ok := metadata.FromIncomingContext(stream.Context())
-	if !ok {
-		return errors.New("metadata is not provided")
-	}
-	if len(md["client-id"]) == 0 {
-		return errors.New("'client-id' is not present in metadata")
+	clientID, err := util.ClientIDFromIncomingCtx(stream.Context())
+	if err != nil {
+		return err
 	}
-	clientID := md["client-id"][0]
 	log := muxServerLog.WithValues("client-id", clientID)
 	log.Info("initializing Kuma Discovery Service (KDS) stream for global-zone sync of resources")
 	session := NewSession(clientID, stream)
@@ -138,7 +144,7 @@ func (s *server) StreamMessage(stream mesh_proto.MultiplexService_StreamMessageS
 		log.Error(err, "closing KDS stream following a callback error")
 		return err
 	}
-	err := <-session.Error()
+	err = <-session.Error()
 	log.Info("KDS stream is closed", "reason", err.Error())
 	return nil
 }
diff --git a/pkg/kds/service/server.go b/pkg/kds/service/server.go
new file mode 100644
index 000000000..a823772f0
--- /dev/null
+++ b/pkg/kds/service/server.go
@@ -0,0 +1,39 @@
+package service
+
+import (
+	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
+	"github.com/kumahq/kuma/pkg/core"
+	"github.com/kumahq/kuma/pkg/kds/util"
+)
+
+type GlobalKDSServiceServer struct {
+	xdsConfigStreams XDSConfigStreams
+}
+
+func NewGlobalKDSServiceServer(xdsConfigStreams XDSConfigStreams) *GlobalKDSServiceServer {
+	return &GlobalKDSServiceServer{
+		xdsConfigStreams: xdsConfigStreams,
+	}
+}
+
+var _ mesh_proto.GlobalKDSServiceServer = &GlobalKDSServiceServer{}
+
+func (g *GlobalKDSServiceServer) StreamXDSConfigs(stream mesh_proto.GlobalKDSService_StreamXDSConfigsServer) error {
+	zone, err := util.ClientIDFromIncomingCtx(stream.Context())
+	if err != nil {
+		return err
+	}
+	core.Log.Info("XDSConfigs stream started", "zone", zone)
+	g.xdsConfigStreams.ZoneConnected(zone, stream)
+	defer g.xdsConfigStreams.ZoneDisconnected(zone)
+	for {
+		resp, err := stream.Recv()
+		if err != nil {
+			return err
+		}
+		core.Log.V(1).Info("XDSConfigs response received", "zone", zone, "requestId", resp.RequestId)
+		if err := g.xdsConfigStreams.ResponseReceived(zone, resp); err != nil {
+			return err
+		}
+	}
+}
diff --git a/pkg/kds/service/xds_config_processor.go b/pkg/kds/service/xds_config_processor.go
new file mode 100644
index 000000000..35e5a9254
--- /dev/null
+++ b/pkg/kds/service/xds_config_processor.go
@@ -0,0 +1,91 @@
+package service
+
+import (
+	"context"
+	"time"
+
+	"github.com/pkg/errors"
+
+	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
+	core_manager "github.com/kumahq/kuma/pkg/core/resources/manager"
+	core_model "github.com/kumahq/kuma/pkg/core/resources/model"
+	"github.com/kumahq/kuma/pkg/core/resources/registry"
+	core_store "github.com/kumahq/kuma/pkg/core/resources/store"
+)
+
+type XDSConfigProcessor interface {
+	StartProcessing(stream mesh_proto.GlobalKDSService_StreamXDSConfigsClient, errorCh chan error)
+}
+
+type ConfigDumpFn = func(ctx context.Context, proxy core_model.ResourceWithAddress) ([]byte, error)
+
+type xdsConfigDumpProcessor struct {
+	resManager   core_manager.ReadOnlyResourceManager
+	configDumpFn ConfigDumpFn
+}
+
+var _ XDSConfigProcessor = &xdsConfigDumpProcessor{}
+
+func NewXDSConfigProcessor(
+	resManager core_manager.ReadOnlyResourceManager,
+	configDumpFn ConfigDumpFn,
+) XDSConfigProcessor {
+	return &xdsConfigDumpProcessor{
+		resManager:   resManager,
+		configDumpFn: configDumpFn,
+	}
+}
+
+func (s *xdsConfigDumpProcessor) StartProcessing(
+	stream mesh_proto.GlobalKDSService_StreamXDSConfigsClient,
+	errorCh chan error,
+) {
+	for {
+		req, err := stream.Recv()
+		if err != nil {
+			errorCh <- err
+			return
+		}
+		go func() { // schedule in the background to be able to quickly process more requests
+			config, err := s.executeConfigDump(stream.Context(), req)
+
+			resp := &mesh_proto.XDSConfigResponse{
+				RequestId: req.RequestId,
+			}
+			if len(config) > 0 {
+				resp.Result = &mesh_proto.XDSConfigResponse_Config{
+					Config: config,
+				}
+			}
+			if err != nil { // send the error to the client instead of terminating stream.
+				resp.Result = &mesh_proto.XDSConfigResponse_Error{
+					Error: err.Error(),
+				}
+			}
+			if err := stream.Send(resp); err != nil {
+				errorCh <- err
+				return
+			}
+		}()
+	}
+}
+
+func (s *xdsConfigDumpProcessor) executeConfigDump(ctx context.Context, req *mesh_proto.XDSConfigRequest) ([]byte, error) {
+	ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
+	defer cancel()
+
+	res, err := registry.Global().NewObject(core_model.ResourceType(req.ResourceType))
+	if err != nil {
+		return nil, err
+	}
+	if err := s.resManager.Get(ctx, res, core_store.GetByKey(req.ResourceName, req.ResourceMesh)); err != nil {
+		return nil, err
+	}
+
+	resWithAddr, ok := res.(core_model.ResourceWithAddress)
+	if !ok {
+		return nil, errors.Errorf("invalid type %T", resWithAddr)
+	}
+
+	return s.configDumpFn(ctx, resWithAddr)
+}
diff --git a/pkg/kds/service/xds_config_streams.go b/pkg/kds/service/xds_config_streams.go
new file mode 100644
index 000000000..28933fd17
--- /dev/null
+++ b/pkg/kds/service/xds_config_streams.go
@@ -0,0 +1,105 @@
+package service
+
+import (
+	"sync"
+
+	"github.com/pkg/errors"
+
+	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
+)
+
+type XDSConfigStreams interface {
+	Send(zone string, req *mesh_proto.XDSConfigRequest) error
+	WatchResponse(zone string, reqID string, resp chan *mesh_proto.XDSConfigResponse) error
+	DeleteWatch(zone string, reqID string)
+
+	ZoneConnected(zone string, stream mesh_proto.GlobalKDSService_StreamXDSConfigsServer)
+	ZoneDisconnected(zone string)
+	ResponseReceived(zone string, resp *mesh_proto.XDSConfigResponse) error
+}
+
+type xdsConfigStreams struct {
+	streamForZone map[string]*xdsConfigStream
+	sync.Mutex    // protects streamForZone
+}
+
+func (x *xdsConfigStreams) ResponseReceived(zone string, resp *mesh_proto.XDSConfigResponse) error {
+	stream, err := x.zoneStream(zone)
+	if err != nil {
+		return err
+	}
+	stream.Lock()
+	ch, ok := stream.watchForRequestId[resp.RequestId]
+	stream.Unlock()
+	if !ok {
+		return errors.Errorf("callback for request Id %s not found", resp.RequestId)
+	}
+	ch <- resp
+	return nil
+}
+
+func NewXdsConfigStreams() XDSConfigStreams {
+	return &xdsConfigStreams{
+		streamForZone: map[string]*xdsConfigStream{},
+	}
+}
+
+func (x *xdsConfigStreams) ZoneConnected(zone string, stream mesh_proto.GlobalKDSService_StreamXDSConfigsServer) {
+	x.Lock()
+	defer x.Unlock()
+	x.streamForZone[zone] = &xdsConfigStream{
+		stream:            stream,
+		watchForRequestId: map[string]chan *mesh_proto.XDSConfigResponse{},
+	}
+}
+
+func (x *xdsConfigStreams) zoneStream(zone string) (*xdsConfigStream, error) {
+	x.Lock()
+	defer x.Unlock()
+	stream, ok := x.streamForZone[zone]
+	if !ok {
+		return nil, errors.Errorf("zone %s is not connected", zone)
+	}
+	return stream, nil
+}
+
+func (x *xdsConfigStreams) ZoneDisconnected(zone string) {
+	x.Lock()
+	defer x.Unlock()
+	delete(x.streamForZone, zone)
+}
+
+type xdsConfigStream struct {
+	stream            mesh_proto.GlobalKDSService_StreamXDSConfigsServer
+	watchForRequestId map[string]chan *mesh_proto.XDSConfigResponse
+	sync.Mutex        // protects watchForRequestId
+}
+
+func (x *xdsConfigStreams) Send(zone string, req *mesh_proto.XDSConfigRequest) error {
+	stream, err := x.zoneStream(zone)
+	if err != nil {
+		return err
+	}
+	return stream.stream.Send(req)
+}
+
+func (x *xdsConfigStreams) WatchResponse(zone string, reqID string, resp chan *mesh_proto.XDSConfigResponse) error {
+	stream, err := x.zoneStream(zone)
+	if err != nil {
+		return err
+	}
+	stream.Lock()
+	defer stream.Unlock()
+	stream.watchForRequestId[reqID] = resp
+	return nil
+}
+
+func (x *xdsConfigStreams) DeleteWatch(zone string, reqID string) {
+	stream, err := x.zoneStream(zone)
+	if err != nil {
+		return // zone was already deleted
+	}
+	stream.Lock()
+	defer stream.Unlock()
+	delete(stream.watchForRequestId, reqID)
+}
diff --git a/pkg/kds/util/client_id.go b/pkg/kds/util/client_id.go
new file mode 100644
index 000000000..7a90457e2
--- /dev/null
+++ b/pkg/kds/util/client_id.go
@@ -0,0 +1,19 @@
+package util
+
+import (
+	"context"
+
+	"github.com/pkg/errors"
+	"google.golang.org/grpc/metadata"
+)
+
+func ClientIDFromIncomingCtx(ctx context.Context) (string, error) {
+	md, ok := metadata.FromIncomingContext(ctx)
+	if !ok {
+		return "", errors.New("metadata is not provided")
+	}
+	if len(md["client-id"]) == 0 {
+		return "", errors.New("'client-id' is not present in metadata")
+	}
+	return md["client-id"][0], nil
+}
diff --git a/pkg/kds/zone/components.go b/pkg/kds/zone/components.go
index 8a2bd3b4b..f00f0e6d1 100644
--- a/pkg/kds/zone/components.go
+++ b/pkg/kds/zone/components.go
@@ -16,6 +16,7 @@ import (
 	kds_client "github.com/kumahq/kuma/pkg/kds/client"
 	"github.com/kumahq/kuma/pkg/kds/mux"
 	kds_server "github.com/kumahq/kuma/pkg/kds/server"
+	"github.com/kumahq/kuma/pkg/kds/service"
 	sync_store "github.com/kumahq/kuma/pkg/kds/store"
 	"github.com/kumahq/kuma/pkg/kds/util"
 	resources_k8s "github.com/kumahq/kuma/pkg/plugins/resources/k8s"
@@ -72,12 +73,13 @@ func Setup(rt core_runtime.Runtime) error {
 		return nil
 	})
 	muxClient := mux.NewClient(
+		rt.KDSContext().ZoneClientCtx,
 		rt.Config().Multizone.Zone.GlobalAddress,
 		zone,
 		onSessionStarted,
 		*rt.Config().Multizone.Zone.KDS,
 		rt.Metrics(),
-		rt.KDSContext().ZoneClientCtx,
+		service.NewXDSConfigProcessor(rt.ReadOnlyResourceManager(), rt.EnvoyAdminClient().ConfigDump),
 	)
 	return rt.Add(component.NewResilientComponent(kdsZoneLog.WithName("kds-mux-client"), muxClient))
 }
diff --git a/pkg/plugins/runtime/k8s/controllers/pod_status_controller.go b/pkg/plugins/runtime/k8s/controllers/pod_status_controller.go
index 9bbca14dd..fbebb82aa 100644
--- a/pkg/plugins/runtime/k8s/controllers/pod_status_controller.go
+++ b/pkg/plugins/runtime/k8s/controllers/pod_status_controller.go
@@ -65,7 +65,7 @@ func (r *PodStatusReconciler) Reconcile(ctx context.Context, req kube_ctrl.Reque
 	}
 
 	var errs error
-	err := r.EnvoyAdminClient.PostQuit(dp)
+	err := r.EnvoyAdminClient.PostQuit(ctx, dp)
 	if err != nil {
 		errs = multierr.Append(errs, errors.Wrapf(err, "envoy admin client failed. Most probably the pod is already going down."))
 	}
diff --git a/pkg/xds/envoy/listeners/filter_chain_builder.go b/pkg/xds/envoy/listeners/filter_chain_builder.go
index 7ce7fc578..e5e9bdf53 100644
--- a/pkg/xds/envoy/listeners/filter_chain_builder.go
+++ b/pkg/xds/envoy/listeners/filter_chain_builder.go
@@ -5,6 +5,7 @@ import (
 	envoy_hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
 	envoy_types "github.com/envoyproxy/go-control-plane/pkg/cache/types"
 	"github.com/pkg/errors"
+	"google.golang.org/protobuf/types/known/anypb"
 
 	"github.com/kumahq/kuma/pkg/xds/envoy"
 	v3 "github.com/kumahq/kuma/pkg/xds/envoy/listeners/v3"
@@ -65,6 +66,11 @@ func (b *FilterChainBuilder) Build() (envoy_types.Resource, error) {
 		_ = v3.UpdateHTTPConnectionManager(&filterChain, func(hcm *envoy_hcm.HttpConnectionManager) error {
 			router := &envoy_hcm.HttpFilter{
 				Name: "envoy.filters.http.router",
+				ConfigType: &envoy_hcm.HttpFilter_TypedConfig{
+					TypedConfig: &anypb.Any{
+						TypeUrl: "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router",
+					},
+				},
 			}
 			hcm.HttpFilters = append(hcm.HttpFilters, router)
 			return nil
diff --git a/pkg/xds/topology/outbound.go b/pkg/xds/topology/outbound.go
index d4229d38a..0a8b0c7df 100644
--- a/pkg/xds/topology/outbound.go
+++ b/pkg/xds/topology/outbound.go
@@ -204,7 +204,15 @@ func fillIngressOutbounds(
 			continue
 		}
 
-		ziInstances[ziCoordinates] = struct{}{}
+		if len(zi.Spec.GetAvailableServices()) > 0 {
+			// Consider squashing instances only if available services are reconciled.
+			// This is necessary to perform graceful join of new instances of ZoneIngress.
+			// When a new instance of ZoneIngress is up, it's immediately synced to all zones.
+			// Only after a short period of time (first XDS reconciliation) it will be updated with AvailableServices.
+			// If we just take any instance, we may choose an instance without AvailableServices as a representation
+			// of all ZoneIngress instances behind one load balancer.
+			ziInstances[ziCoordinates] = struct{}{}
+		}
 
 		for _, service := range zi.Spec.GetAvailableServices() {
 			if service.Mesh != mesh.GetMeta().GetName() {
diff --git a/tools/envoy/Dockerfile.build-centos b/tools/envoy/Dockerfile.build-centos
index 72f6e129e..63cf1a44b 100644
--- a/tools/envoy/Dockerfile.build-centos
+++ b/tools/envoy/Dockerfile.build-centos
@@ -3,16 +3,6 @@ FROM $ENVOY_BUILD_IMAGE
 
 ARG BUILD_CMD
 
-# based on the fix https://github.com/envoyproxy/envoy/pull/18426, could be deleted as soon as it'll be merged
-RUN git clone https://gn.googlesource.com/gn && \
-    pushd gn && \
-    git checkout 45aa842fb41d79e149b46fac8ad71728856e15b9 && \
-    python build/gen.py && \
-    ninja -C out && \
-    mv -f out/gn /usr/local/bin/gn && \
-    chmod +x /usr/local/bin/gn && \
-    popd
-
 COPY . /envoy-sources/
 
 RUN bash -c "pushd envoy-sources/ && bazel/setup_clang.sh /opt/llvm"
diff --git a/tools/envoy/build_centos.sh b/tools/envoy/build_centos.sh
index d7597e231..d05528b1e 100755
--- a/tools/envoy/build_centos.sh
+++ b/tools/envoy/build_centos.sh
@@ -9,6 +9,7 @@ echo "Building Envoy for CentOS 7"
 mkdir -p "$(dirname "${BINARY_PATH}")"
 
 SOURCE_DIR="${SOURCE_DIR}" "${KUMA_DIR:-.}/tools/envoy/fetch_sources.sh"
+CONTRIB_ENABLED_MATRIX_SCRIPT=$(realpath "${KUMA_DIR:-.}/tools/envoy/contrib_enabled_matrix.py")
 
 BAZEL_BUILD_EXTRA_OPTIONS=${BAZEL_BUILD_EXTRA_OPTIONS:-""}
 read -ra BAZEL_BUILD_EXTRA_OPTIONS <<< "${BAZEL_BUILD_EXTRA_OPTIONS}"
@@ -17,7 +18,12 @@ BAZEL_BUILD_OPTIONS=(
     "--verbose_failures"
     "${BAZEL_BUILD_EXTRA_OPTIONS[@]+"${BAZEL_BUILD_EXTRA_OPTIONS[@]}"}")
 BUILD_TARGET=${BUILD_TARGET:-"//contrib/exe:envoy-static"}
-BUILD_CMD=${BUILD_CMD:-"bazel build ${BAZEL_BUILD_OPTIONS[@]} -c opt ${BUILD_TARGET} --//source/extensions/transport_sockets/tcp_stats:enabled=false"}
+
+pushd "${SOURCE_DIR}"
+CONTRIB_ENABLED_ARGS=$(python "${CONTRIB_ENABLED_MATRIX_SCRIPT}")
+popd
+
+BUILD_CMD=${BUILD_CMD:-"bazel build ${BAZEL_BUILD_OPTIONS[@]} -c opt ${BUILD_TARGET} ${CONTRIB_ENABLED_ARGS} --//source/extensions/transport_sockets/tcp_stats:enabled=false"}
 
 ENVOY_BUILD_SHA=$(curl --fail --location --silent https://raw.githubusercontent.com/envoyproxy/envoy/"${ENVOY_TAG}"/.bazelrc | grep envoyproxy/envoy-build-ubuntu | sed -e 's#.*envoyproxy/envoy-build-ubuntu:\(.*\)#\1#'| uniq)
 ENVOY_BUILD_IMAGE="envoyproxy/envoy-build-centos:${ENVOY_BUILD_SHA}"
diff --git a/tools/envoy/build_darwin.sh b/tools/envoy/build_darwin.sh
index 43f63b9a6..fb5d85c0a 100755
--- a/tools/envoy/build_darwin.sh
+++ b/tools/envoy/build_darwin.sh
@@ -9,6 +9,7 @@ echo "Building Envoy for Darwin"
 mkdir -p "$(dirname "${BINARY_PATH}")"
 
 SOURCE_DIR="${SOURCE_DIR}" "${KUMA_DIR:-.}/tools/envoy/fetch_sources.sh"
+CONTRIB_ENABLED_MATRIX_SCRIPT=$(realpath "${KUMA_DIR:-.}/tools/envoy/contrib_enabled_matrix.py")
 
 pushd "${SOURCE_DIR}"
 
@@ -22,7 +23,11 @@ BAZEL_BUILD_OPTIONS=(
     "--action_env=PATH=/usr/local/bin:/opt/local/bin:/usr/bin:/bin:/opt/homebrew/bin"
     "--define" "wasm=disabled"
     "${BAZEL_BUILD_EXTRA_OPTIONS[@]+"${BAZEL_BUILD_EXTRA_OPTIONS[@]}"}")
-bazel build "${BAZEL_BUILD_OPTIONS[@]}" -c opt //contrib/exe:envoy-static
+
+CONTRIB_ENABLED_ARGS=$(python "${CONTRIB_ENABLED_MATRIX_SCRIPT}")
+
+# shellcheck disable=SC2086
+bazel build "${BAZEL_BUILD_OPTIONS[@]}" -c opt //contrib/exe:envoy-static ${CONTRIB_ENABLED_ARGS}
 
 popd
 
diff --git a/tools/envoy/build_linux.sh b/tools/envoy/build_linux.sh
index cbce8a7ac..447b96dd4 100755
--- a/tools/envoy/build_linux.sh
+++ b/tools/envoy/build_linux.sh
@@ -9,6 +9,7 @@ echo "Building Envoy for Linux"
 mkdir -p "$(dirname "${BINARY_PATH}")"
 
 SOURCE_DIR="${SOURCE_DIR}" "${KUMA_DIR:-.}/tools/envoy/fetch_sources.sh"
+CONTRIB_ENABLED_MATRIX_SCRIPT=$(realpath "${KUMA_DIR:-.}/tools/envoy/contrib_enabled_matrix.py")
 
 BAZEL_BUILD_EXTRA_OPTIONS=${BAZEL_BUILD_EXTRA_OPTIONS:-""}
 read -ra BAZEL_BUILD_EXTRA_OPTIONS <<< "${BAZEL_BUILD_EXTRA_OPTIONS}"
@@ -17,7 +18,12 @@ BAZEL_BUILD_OPTIONS=(
     "--verbose_failures"
     "${BAZEL_BUILD_EXTRA_OPTIONS[@]+"${BAZEL_BUILD_EXTRA_OPTIONS[@]}"}")
 BUILD_TARGET=${BUILD_TARGET:-"//contrib/exe:envoy-static"}
-BUILD_CMD=${BUILD_CMD:-"bazel build ${BAZEL_BUILD_OPTIONS[@]} -c opt ${BUILD_TARGET}"}
+
+pushd "${SOURCE_DIR}"
+CONTRIB_ENABLED_ARGS=$(python "${CONTRIB_ENABLED_MATRIX_SCRIPT}")
+popd
+
+BUILD_CMD=${BUILD_CMD:-"bazel build ${BAZEL_BUILD_OPTIONS[@]} -c opt ${BUILD_TARGET} ${CONTRIB_ENABLED_ARGS}"}
 
 ENVOY_BUILD_SHA=$(curl --fail --location --silent https://raw.githubusercontent.com/envoyproxy/envoy/"${ENVOY_TAG}"/.bazelrc | grep envoyproxy/envoy-build-ubuntu | sed -e 's#.*envoyproxy/envoy-build-ubuntu:\(.*\)#\1#'| uniq)
 ENVOY_BUILD_IMAGE="envoyproxy/envoy-build-ubuntu:${ENVOY_BUILD_SHA}"
diff --git a/tools/envoy/contrib_enabled_matrix.py b/tools/envoy/contrib_enabled_matrix.py
new file mode 100644
index 000000000..31e877a23
--- /dev/null
+++ b/tools/envoy/contrib_enabled_matrix.py
@@ -0,0 +1,17 @@
+#!/usr/bin/python
+
+# file in format CONTRIB_EXTENSIONS = {...}
+exec(open('contrib/contrib_build_config.bzl').read())
+
+enabled = [
+    "envoy.filters.network.kafka_broker"
+]
+
+disabled = []
+for k, v in CONTRIB_EXTENSIONS.items():
+    disabled.append('--{target}:enabled={enabled}'.format(
+        target=v.split(":")[0],
+        enabled=(k in enabled))
+    )
+
+print(' '.join(disabled))
diff --git a/tools/envoy/fetch.sh b/tools/envoy/fetch.sh
index d0dcffae1..bc39b0b27 100755
--- a/tools/envoy/fetch.sh
+++ b/tools/envoy/fetch.sh
@@ -33,14 +33,8 @@ function download_envoy() {
     [ -f "${BINARY_PATH}" ] && chmod +x "${BINARY_PATH}"
 }
 
-if [[ "${GOARCH}" == "arm64" ]]; then
-  ARCH="-arm64"
-else
-  ARCH=""
-fi
-
 if [[ -n "${ENVOY_VERSION}" ]]; then
-  BINARY_NAME="envoy-${ENVOY_VERSION}-${ENVOY_DISTRO}${ARCH}"
+  BINARY_NAME="envoy-${ENVOY_VERSION}-${ENVOY_DISTRO}-${GOARCH}"
   download_envoy "${BINARY_NAME}"
   exit 0
 fi
diff --git a/tools/envoy/version.sh b/tools/envoy/version.sh
index f7ad197d8..9826cdc3e 100755
--- a/tools/envoy/version.sh
+++ b/tools/envoy/version.sh
@@ -9,7 +9,13 @@ set -o nounset
 # - if ENVOY_TAG is a commit hash then the version will look like '1.20.1-dev-b16d390f'
 
 ENVOY_TAG=$1
-ENVOY_VERSION=$(curl --silent --location --fail "https://raw.githubusercontent.com/envoyproxy/envoy/${ENVOY_TAG}/VERSION")
+ENVOY_VERSION=$(curl --silent --location "https://raw.githubusercontent.com/envoyproxy/envoy/${ENVOY_TAG}/VERSION.txt")
+
+# for envoy versions older than v1.22.0 file 'VERSION.txt' used to be called 'VERSION'
+if [[ "${ENVOY_VERSION}" == "404: Not Found" ]]; then
+  ENVOY_VERSION=$(curl --silent --location --fail "https://raw.githubusercontent.com/envoyproxy/envoy/${ENVOY_TAG}/VERSION")
+fi
+
 if [[ "${ENVOY_TAG}" =~ ^v[0-9]*\.[0-9]*\.[0-9]*$ ]]; then
   echo "${ENVOY_VERSION}"
 else
diff --git a/tools/releases/distros.sh b/tools/releases/distros.sh
index b9dd5ea20..52b5744a9 100755
--- a/tools/releases/distros.sh
+++ b/tools/releases/distros.sh
@@ -16,7 +16,7 @@ PULP_HOST="https://api.pulp.konnect-prod.konghq.com"
 PULP_PACKAGE_TYPE="mesh"
 PULP_DIST_NAME="alpine"
 [ -z "$RELEASE_NAME" ] && RELEASE_NAME="kuma"
-ENVOY_VERSION=1.21.1
+ENVOY_VERSION=1.22.0
 [ -z "$KUMA_CONFIG_PATH" ] && KUMA_CONFIG_PATH=pkg/config/app/kuma-cp/kuma-cp.defaults.yaml
 CTL_NAME="kumactl"
 
diff --git a/tools/releases/docker.sh b/tools/releases/docker.sh
index f9d261f26..3fa960988 100755
--- a/tools/releases/docker.sh
+++ b/tools/releases/docker.sh
@@ -8,7 +8,7 @@ source "${SCRIPT_DIR}/../common.sh"
 KUMA_DOCKER_REPO="${KUMA_DOCKER_REPO:-docker.io}"
 KUMA_DOCKER_REPO_ORG="${KUMA_DOCKER_REPO_ORG:-${KUMA_DOCKER_REPO}/kumahq}"
 KUMA_COMPONENTS="${KUMA_COMPONENTS:-kuma-cp kuma-dp kumactl kuma-init kuma-prometheus-sd}"
-ENVOY_VERSION="${ENVOY_VERSION:-1.21.1}"
+ENVOY_VERSION="${ENVOY_VERSION:-1.22.0}"
 BUILD_ARCH="${BUILD_ARCH:-amd64 arm64}"
 
 function build() {


Test patch:
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.cni-enabled.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.cni-enabled.golden.yaml
index 05bee5e50..8cd01f033 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.cni-enabled.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.cni-enabled.golden.yaml
@@ -1704,7 +1704,7 @@ spec:
       terminationGracePeriodSeconds: 5
       containers:
         - name: install-cni
-          image: "docker.io/kumahq/install-cni:0.0.9"
+          image: "docker.io/kumahq/install-cni:0.0.10"
           imagePullPolicy: Always
           command: ["/install-cni.sh"]
           env:
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.dump-values.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.dump-values.yaml
index 0cf4544f4..65b094f2f 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.dump-values.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.dump-values.yaml
@@ -242,7 +242,7 @@ cni:
     # -- CNI image repository
     repository: "kumahq/install-cni"
     # -- CNI image tag
-    tag: "0.0.9"
+    tag: "0.0.10"
 
   # -- Security context at the pod level for cni
   podSecurityContext: {}
@@ -395,8 +395,8 @@ kumactl:
     tag:
 
 kubectl:
-  # rancher maintains an image for all k8s versions */ } }
-  # see: https://hub.docker.com/r/rancher/kubectl */ } }
+  # kuma image that support v1.20.15 image */ } }
+  # see: https://hub.docker.com/r/kumahq/kubectl */ } }
   image:
     # -- The kubectl image registry
     registry: kumahq
diff --git a/pkg/api-server/inspect_endpoints_test.go b/pkg/api-server/inspect_endpoints_test.go
index ab308feb1..444e61a3c 100644
--- a/pkg/api-server/inspect_endpoints_test.go
+++ b/pkg/api-server/inspect_endpoints_test.go
@@ -1106,12 +1106,32 @@ var _ = Describe("Inspect WS", func() {
 		Entry("inspect xds for zone ingress on global", testCase{
 			global:     true,
 			path:       "/zoneingresses/zi-1/xds",
-			goldenFile: "inspect_xds_global_zoneingress.json",
+			goldenFile: "inspect_xds_local_zoneingress.json",
+			resources: []core_model.Resource{
+				newZoneIngress().
+					meta("zi-1").
+					zone(""). // local zone ingress has empty "zone" field
+					admin(2201).
+					address("2.2.2.2").port(8080).
+					advertisedAddress("3.3.3.3").advertisedPort(80).
+					build(),
+			},
 		}),
 		Entry("inspect xds for dataplane on global", testCase{
 			global:     true,
-			path:       "/meshes/default/dataplanes/dp-1/xds",
-			goldenFile: "inspect_xds_global_dataplane.json",
+			path:       "/meshes/mesh-1/dataplanes/backend-1/xds",
+			goldenFile: "inspect_xds_dataplane.json",
+			resources: []core_model.Resource{
+				newMesh("mesh-1"),
+				newDataplane().
+					meta("backend-1", "mesh-1").
+					admin(3301).
+					inbound80to81("backend", "192.168.0.1").
+					outbound8080("redis", "192.168.0.2").
+					outbound8080("gateway", "192.168.0.3").
+					outbound8080("web", "192.168.0.4").
+					build(),
+			},
 		}),
 		Entry("inspect xds for zone egress", testCase{
 			path:       "/zoneegresses/ze-1/xds",
diff --git a/pkg/envoy/admin/kds_client_test.go b/pkg/envoy/admin/kds_client_test.go
new file mode 100644
index 000000000..193d3da19
--- /dev/null
+++ b/pkg/envoy/admin/kds_client_test.go
@@ -0,0 +1,146 @@
+package admin_test
+
+import (
+	"context"
+	"time"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	"google.golang.org/grpc"
+
+	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/envoy/admin"
+	"github.com/kumahq/kuma/pkg/kds/service"
+	test_model "github.com/kumahq/kuma/pkg/test/resources/model"
+)
+
+var _ = Describe("KDS client", func() {
+
+	streams := service.NewXdsConfigStreams()
+	client := admin.NewKDSEnvoyAdminClient(streams)
+
+	zoneName := "zone-1"
+	var stream *mockStream
+
+	BeforeEach(func() {
+		stream = &mockStream{
+			receivedRequests: make(chan *mesh_proto.XDSConfigRequest, 1),
+		}
+		streams.ZoneConnected(zoneName, stream)
+	})
+
+	It("should execute config dump", func() {
+		// given
+		dpRes := core_mesh.NewDataplaneResource()
+		dpRes.SetMeta(&test_model.ResourceMeta{
+			Mesh: "default",
+			Name: "zone-1.dp-1",
+		})
+		configContent := []byte("config")
+
+		// when
+		respCh := make(chan []byte)
+		go func() {
+			defer GinkgoRecover()
+			resp, err := client.ConfigDump(context.Background(), dpRes)
+			Expect(err).To(Succeed())
+			respCh <- resp
+		}()
+
+		// and
+		request := <-stream.receivedRequests
+		err := streams.ResponseReceived(zoneName, &mesh_proto.XDSConfigResponse{
+			RequestId: request.RequestId,
+			Result: &mesh_proto.XDSConfigResponse_Config{
+				Config: configContent,
+			},
+		})
+
+		// then
+		Expect(err).ToNot(HaveOccurred())
+		resp := <-respCh
+		Expect(resp).To(Equal(configContent))
+	})
+
+	It("should fail when zone is not connected", func() {
+		// given
+		dpRes := core_mesh.NewDataplaneResource()
+		dpRes.SetMeta(&test_model.ResourceMeta{
+			Mesh: "default",
+			Name: "not-connected.dp-1",
+		})
+
+		// when
+		_, err := client.ConfigDump(context.Background(), dpRes)
+
+		// then
+		Expect(err).To(MatchError("could not send XDSConfigRequest: zone not-connected is not connected"))
+	})
+
+	It("should time out after X seconds", func() {
+		// given
+		dpRes := core_mesh.NewDataplaneResource()
+		dpRes.SetMeta(&test_model.ResourceMeta{
+			Mesh: "default",
+			Name: zoneName + ".dp-1",
+		})
+
+		ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+		defer cancel()
+
+		// when
+		_, err := client.ConfigDump(ctx, dpRes)
+
+		// then
+		Expect(err).To(MatchError("timeout"))
+	})
+
+	It("should rethrow error from zone CP", func() {
+		// given
+		dpRes := core_mesh.NewDataplaneResource()
+		dpRes.SetMeta(&test_model.ResourceMeta{
+			Mesh: "default",
+			Name: zoneName + ".dp-1",
+		})
+
+		// when
+		errCh := make(chan error)
+		go func() {
+			defer GinkgoRecover()
+			_, err := client.ConfigDump(context.Background(), dpRes)
+			errCh <- err
+		}()
+
+		// and
+		request := <-stream.receivedRequests
+		err := streams.ResponseReceived(zoneName, &mesh_proto.XDSConfigResponse{
+			RequestId: request.RequestId,
+			Result: &mesh_proto.XDSConfigResponse_Error{
+				Error: "failed",
+			},
+		})
+
+		// then
+		Expect(err).ToNot(HaveOccurred())
+		err = <-errCh
+		Expect(err).To(MatchError("error response from Zone CP: failed"))
+	})
+
+})
+
+type mockStream struct {
+	receivedRequests  chan *mesh_proto.XDSConfigRequest
+	grpc.ServerStream // nil to implement methods
+}
+
+func (m *mockStream) Send(request *mesh_proto.XDSConfigRequest) error {
+	m.receivedRequests <- request
+	return nil
+}
+
+func (m *mockStream) Recv() (*mesh_proto.XDSConfigResponse, error) {
+	return nil, nil
+}
+
+var _ mesh_proto.GlobalKDSService_StreamXDSConfigsServer = &mockStream{}
diff --git a/pkg/plugins/runtime/gateway/testdata/01-gateway-listener.yaml b/pkg/plugins/runtime/gateway/testdata/01-gateway-listener.yaml
index 493199021..f72c125e8 100644
--- a/pkg/plugins/runtime/gateway/testdata/01-gateway-listener.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/01-gateway-listener.yaml
@@ -33,6 +33,8 @@ Resources:
               responseDirectionConfig:
                 disableOnEtagHeader: true
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           mergeSlashes: true
           normalizePath: true
           rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/02-gateway-listener.yaml b/pkg/plugins/runtime/gateway/testdata/02-gateway-listener.yaml
index 447a6d1a0..c20790896 100644
--- a/pkg/plugins/runtime/gateway/testdata/02-gateway-listener.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/02-gateway-listener.yaml
@@ -33,6 +33,8 @@ Resources:
               responseDirectionConfig:
                 disableOnEtagHeader: true
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           mergeSlashes: true
           normalizePath: true
           rds:
@@ -86,6 +88,8 @@ Resources:
               responseDirectionConfig:
                 disableOnEtagHeader: true
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           mergeSlashes: true
           normalizePath: true
           rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/03-gateway-listener.yaml b/pkg/plugins/runtime/gateway/testdata/03-gateway-listener.yaml
index 1450d92af..76ffdd308 100644
--- a/pkg/plugins/runtime/gateway/testdata/03-gateway-listener.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/03-gateway-listener.yaml
@@ -33,6 +33,8 @@ Resources:
               responseDirectionConfig:
                 disableOnEtagHeader: true
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           mergeSlashes: true
           normalizePath: true
           rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/04-gateway-listener.yaml b/pkg/plugins/runtime/gateway/testdata/04-gateway-listener.yaml
index 3c657046e..5be7a1844 100644
--- a/pkg/plugins/runtime/gateway/testdata/04-gateway-listener.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/04-gateway-listener.yaml
@@ -42,6 +42,8 @@ Resources:
               responseDirectionConfig:
                 disableOnEtagHeader: true
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           mergeSlashes: true
           normalizePath: true
           rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/05-gateway-listener.yaml b/pkg/plugins/runtime/gateway/testdata/05-gateway-listener.yaml
index 0535f56bd..787043fa1 100644
--- a/pkg/plugins/runtime/gateway/testdata/05-gateway-listener.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/05-gateway-listener.yaml
@@ -37,6 +37,8 @@ Resources:
               responseDirectionConfig:
                 disableOnEtagHeader: true
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           mergeSlashes: true
           normalizePath: true
           rds:
@@ -96,6 +98,8 @@ Resources:
               responseDirectionConfig:
                 disableOnEtagHeader: true
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           mergeSlashes: true
           normalizePath: true
           rds:
@@ -155,6 +159,8 @@ Resources:
               responseDirectionConfig:
                 disableOnEtagHeader: true
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           mergeSlashes: true
           normalizePath: true
           rds:
@@ -212,6 +218,8 @@ Resources:
               responseDirectionConfig:
                 disableOnEtagHeader: true
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           mergeSlashes: true
           normalizePath: true
           rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/01-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/01-gateway-route.yaml
index 9c27ca42a..c0cb3f480 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/01-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/01-gateway-route.yaml
@@ -81,6 +81,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/02-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/02-gateway-route.yaml
index c7578adfc..978ff0327 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/02-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/02-gateway-route.yaml
@@ -81,6 +81,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/03-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/03-gateway-route.yaml
index edb412fe7..ed9d03018 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/03-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/03-gateway-route.yaml
@@ -81,6 +81,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/04-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/04-gateway-route.yaml
index 5b9cada3d..ba4579ee2 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/04-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/04-gateway-route.yaml
@@ -110,6 +110,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/05-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/05-gateway-route.yaml
index dccd7d589..af72fdde2 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/05-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/05-gateway-route.yaml
@@ -38,6 +38,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/06-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/06-gateway-route.yaml
index c4baf9755..4390c33f9 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/06-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/06-gateway-route.yaml
@@ -110,6 +110,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/07-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/07-gateway-route.yaml
index 12025cd81..da388e52c 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/07-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/07-gateway-route.yaml
@@ -81,6 +81,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/08-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/08-gateway-route.yaml
index e27fa1379..da2745b59 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/08-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/08-gateway-route.yaml
@@ -124,6 +124,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/09-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/09-gateway-route.yaml
index 3b5efc3a2..e5c1fff7b 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/09-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/09-gateway-route.yaml
@@ -81,6 +81,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/10-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/10-gateway-route.yaml
index e8bf196ae..d59105279 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/10-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/10-gateway-route.yaml
@@ -124,6 +124,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/11-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/11-gateway-route.yaml
index c5b3b171b..9d02dcf6e 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/11-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/11-gateway-route.yaml
@@ -124,6 +124,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/12-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/12-gateway-route.yaml
index 3744c70b6..169eae066 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/12-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/12-gateway-route.yaml
@@ -81,6 +81,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/13-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/13-gateway-route.yaml
index a94312267..54923291e 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/13-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/13-gateway-route.yaml
@@ -167,6 +167,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/14-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/14-gateway-route.yaml
index 83611df78..36a5ca778 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/14-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/14-gateway-route.yaml
@@ -81,6 +81,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/15-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/15-gateway-route.yaml
index 3796d331f..e4b8d0cfe 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/15-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/15-gateway-route.yaml
@@ -167,6 +167,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/16-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/16-gateway-route.yaml
index 361b41d74..41475a9b8 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/16-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/16-gateway-route.yaml
@@ -164,6 +164,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/17-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/17-gateway-route.yaml
index 8225bfe80..a60e449a2 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/17-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/17-gateway-route.yaml
@@ -185,6 +185,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/18-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/18-gateway-route.yaml
index cf9f38392..2e407a77b 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/18-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/18-gateway-route.yaml
@@ -78,6 +78,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/19-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/19-gateway-route.yaml
index b071d8812..f915c2dbe 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/19-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/19-gateway-route.yaml
@@ -90,6 +90,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/20-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/20-gateway-route.yaml
index c8c4edae0..d210f8d86 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/20-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/20-gateway-route.yaml
@@ -167,6 +167,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/21-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/21-gateway-route.yaml
index a9c5c7d94..f7050f12b 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/21-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/21-gateway-route.yaml
@@ -167,6 +167,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/22-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/22-gateway-route.yaml
index 7e20b626a..475eae0da 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/22-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/22-gateway-route.yaml
@@ -124,6 +124,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/23-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/23-gateway-route.yaml
index 5bd6c5064..f6c1c5446 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/23-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/23-gateway-route.yaml
@@ -124,6 +124,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/24-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/24-gateway-route.yaml
index 5bd6c5064..f6c1c5446 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/24-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/24-gateway-route.yaml
@@ -124,6 +124,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/25-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/25-gateway-route.yaml
index 42e9243b2..f792eb768 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/25-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/25-gateway-route.yaml
@@ -103,6 +103,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/http/26-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/http/26-gateway-route.yaml
index 689e0a2d2..f0045013e 100644
--- a/pkg/plugins/runtime/gateway/testdata/http/26-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/http/26-gateway-route.yaml
@@ -89,6 +89,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/01-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/01-gateway-route.yaml
index 9c27ca42a..c0cb3f480 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/01-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/01-gateway-route.yaml
@@ -81,6 +81,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/02-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/02-gateway-route.yaml
index c7578adfc..978ff0327 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/02-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/02-gateway-route.yaml
@@ -81,6 +81,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/03-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/03-gateway-route.yaml
index f8bc25767..c9e1b888d 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/03-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/03-gateway-route.yaml
@@ -85,6 +85,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/04-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/04-gateway-route.yaml
index c6a7208bc..b8d8e7cd6 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/04-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/04-gateway-route.yaml
@@ -114,6 +114,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/05-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/05-gateway-route.yaml
index b5c2648b5..dd5f463c2 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/05-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/05-gateway-route.yaml
@@ -42,6 +42,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/06-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/06-gateway-route.yaml
index 5f1220c40..dd83eb2a0 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/06-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/06-gateway-route.yaml
@@ -114,6 +114,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/07-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/07-gateway-route.yaml
index 430ce6c4f..7d143372d 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/07-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/07-gateway-route.yaml
@@ -85,6 +85,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/08-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/08-gateway-route.yaml
index 1bbb9ad51..c7c67f22d 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/08-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/08-gateway-route.yaml
@@ -128,6 +128,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/09-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/09-gateway-route.yaml
index f3c0d592e..a5bcf005a 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/09-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/09-gateway-route.yaml
@@ -85,6 +85,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/10-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/10-gateway-route.yaml
index 91b3c6685..945e3b8c9 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/10-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/10-gateway-route.yaml
@@ -128,6 +128,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/11-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/11-gateway-route.yaml
index 36dba3a0b..4edcc1fff 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/11-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/11-gateway-route.yaml
@@ -128,6 +128,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/12-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/12-gateway-route.yaml
index 51d0027d7..212d12033 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/12-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/12-gateway-route.yaml
@@ -85,6 +85,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/13-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/13-gateway-route.yaml
index 9aed2a908..a4cffee93 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/13-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/13-gateway-route.yaml
@@ -171,6 +171,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/14-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/14-gateway-route.yaml
index 28ec8b7b0..c944bc04a 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/14-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/14-gateway-route.yaml
@@ -85,6 +85,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/15-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/15-gateway-route.yaml
index 7c417827e..9f76d3e01 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/15-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/15-gateway-route.yaml
@@ -171,6 +171,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/16-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/16-gateway-route.yaml
index b581f0ee0..77946e765 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/16-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/16-gateway-route.yaml
@@ -168,6 +168,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/17-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/17-gateway-route.yaml
index 50d4160a9..6a53f2dd1 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/17-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/17-gateway-route.yaml
@@ -189,6 +189,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/18-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/18-gateway-route.yaml
index ac888636b..a830676c8 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/18-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/18-gateway-route.yaml
@@ -82,6 +82,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/19-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/19-gateway-route.yaml
index 741e3adff..caa695ad1 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/19-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/19-gateway-route.yaml
@@ -94,6 +94,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/20-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/20-gateway-route.yaml
index 58b043752..853c9acda 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/20-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/20-gateway-route.yaml
@@ -171,6 +171,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
@@ -230,6 +232,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
@@ -289,6 +293,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/21-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/21-gateway-route.yaml
index 4014f458f..0ec93b0dd 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/21-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/21-gateway-route.yaml
@@ -171,6 +171,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/22-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/22-gateway-route.yaml
index 7e20b626a..475eae0da 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/22-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/22-gateway-route.yaml
@@ -124,6 +124,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/23-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/23-gateway-route.yaml
index 5bd6c5064..f6c1c5446 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/23-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/23-gateway-route.yaml
@@ -124,6 +124,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/24-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/24-gateway-route.yaml
index 5bd6c5064..f6c1c5446 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/24-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/24-gateway-route.yaml
@@ -124,6 +124,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/25-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/25-gateway-route.yaml
index e0c075fa1..9ebb3bcfc 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/25-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/25-gateway-route.yaml
@@ -107,6 +107,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/plugins/runtime/gateway/testdata/https/26-gateway-route.yaml b/pkg/plugins/runtime/gateway/testdata/https/26-gateway-route.yaml
index 73262bad6..3dd49ad6d 100644
--- a/pkg/plugins/runtime/gateway/testdata/https/26-gateway-route.yaml
+++ b/pkg/plugins/runtime/gateway/testdata/https/26-gateway-route.yaml
@@ -93,6 +93,8 @@ Listeners:
                 responseDirectionConfig:
                   disableOnEtagHeader: true
             - name: envoy.filters.http.router
+              typedConfig:
+                '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
             mergeSlashes: true
             normalizePath: true
             rds:
diff --git a/pkg/test/runtime/runtime.go b/pkg/test/runtime/runtime.go
index 52ec2e957..2e3b8626e 100644
--- a/pkg/test/runtime/runtime.go
+++ b/pkg/test/runtime/runtime.go
@@ -26,7 +26,6 @@ import (
 	secret_manager "github.com/kumahq/kuma/pkg/core/secrets/manager"
 	secret_store "github.com/kumahq/kuma/pkg/core/secrets/store"
 	"github.com/kumahq/kuma/pkg/dp-server/server"
-	"github.com/kumahq/kuma/pkg/envoy/admin"
 	"github.com/kumahq/kuma/pkg/events"
 	kds_context "github.com/kumahq/kuma/pkg/kds/context"
 	"github.com/kumahq/kuma/pkg/metrics"
@@ -145,7 +144,7 @@ func (d *DummyEnvoyAdminClient) GenerateAPIToken(dp *core_mesh.DataplaneResource
 	return "token", nil
 }
 
-func (d *DummyEnvoyAdminClient) PostQuit(dataplane *core_mesh.DataplaneResource) error {
+func (d *DummyEnvoyAdminClient) PostQuit(ctx context.Context, dataplane *core_mesh.DataplaneResource) error {
 	if d.PostQuitCalled != nil {
 		*d.PostQuitCalled++
 	}
@@ -153,6 +152,6 @@ func (d *DummyEnvoyAdminClient) PostQuit(dataplane *core_mesh.DataplaneResource)
 	return nil
 }
 
-func (d *DummyEnvoyAdminClient) ConfigDump(proxy admin.ResourceWithAddress, defaultAdminPort uint32) ([]byte, error) {
-	return []byte(fmt.Sprintf(`{"envoyAdminAddress": "%s"}`, proxy.AdminAddress(defaultAdminPort))), nil
+func (d *DummyEnvoyAdminClient) ConfigDump(ctx context.Context, proxy core_model.ResourceWithAddress) ([]byte, error) {
+	return []byte(fmt.Sprintf(`{"envoyAdminAddress": "%s"}`, proxy.AdminAddress(9901))), nil
 }
diff --git a/pkg/xds/envoy/listeners/http_connection_manager_configurer_test.go b/pkg/xds/envoy/listeners/http_connection_manager_configurer_test.go
index ce36fc1be..4566adf53 100644
--- a/pkg/xds/envoy/listeners/http_connection_manager_configurer_test.go
+++ b/pkg/xds/envoy/listeners/http_connection_manager_configurer_test.go
@@ -47,6 +47,8 @@ var _ = Describe("HttpConnectionManager Configurers", func() {
               '@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
               serverName: test-server
               statPrefix: test`,
 			}),
@@ -60,6 +62,8 @@ var _ = Describe("HttpConnectionManager Configurers", func() {
               '@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
               mergeSlashes: true
               normalizePath: true
               statPrefix: test`,
@@ -74,6 +78,8 @@ var _ = Describe("HttpConnectionManager Configurers", func() {
               '@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: test
               stripAnyHostPort: true`,
 			}),
diff --git a/pkg/xds/envoy/listeners/v3/fault_injection_configurer_test.go b/pkg/xds/envoy/listeners/v3/fault_injection_configurer_test.go
index c32c52ec1..93ef56d73 100644
--- a/pkg/xds/envoy/listeners/v3/fault_injection_configurer_test.go
+++ b/pkg/xds/envoy/listeners/v3/fault_injection_configurer_test.go
@@ -72,6 +72,8 @@ var _ = Describe("FaultInjectionConfigurer", func() {
                         googleRe2: {}
                         regex: '.*&tag1=[^&]*value1[,&].*&tag2=[^&]*value2[,&].*'
                 - name: envoy.filters.http.router
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                 statPrefix: stats`,
 		}),
 		Entry("2 policy selectors", testCase{
@@ -118,6 +120,8 @@ var _ = Describe("FaultInjectionConfigurer", func() {
                         googleRe2: {}
                         regex: '(.*&tag1=[^&]*value1m1[,&].*&tag2=[^&]*value2m1[,&].*|.*&tag1=[^&]*value1m2[,&].*&tag2=[^&]*value2m2[,&].*)'
                 - name: envoy.filters.http.router
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                 statPrefix: stats`,
 		}),
 		Entry("should preserve the order of passed policies", testCase{
@@ -221,6 +225,8 @@ var _ = Describe("FaultInjectionConfigurer", func() {
                          googleRe2: {}
                          regex: .*&tag1=.*&tag2=.*
                  - name: envoy.filters.http.router
+                   typedConfig:
+                     '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                  statPrefix: stats`,
 		}),
 	)
diff --git a/pkg/xds/envoy/listeners/v3/grpc_stats_configurer_test.go b/pkg/xds/envoy/listeners/v3/grpc_stats_configurer_test.go
index b659038cf..f99a368c2 100644
--- a/pkg/xds/envoy/listeners/v3/grpc_stats_configurer_test.go
+++ b/pkg/xds/envoy/listeners/v3/grpc_stats_configurer_test.go
@@ -41,6 +41,8 @@ var _ = Describe("gRPCStatsConfigurer", func() {
                     emitFilterState: true
                     statsForAllMethods: true
                 - name: envoy.filters.http.router
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                 statPrefix: stats`,
 		}),
 	)
diff --git a/pkg/xds/envoy/listeners/v3/http_access_log_configurer_test.go b/pkg/xds/envoy/listeners/v3/http_access_log_configurer_test.go
index 08ebefe39..6965f7ba3 100644
--- a/pkg/xds/envoy/listeners/v3/http_access_log_configurer_test.go
+++ b/pkg/xds/envoy/listeners/v3/http_access_log_configurer_test.go
@@ -89,6 +89,8 @@ var _ = Describe("HttpAccessLogConfigurer", func() {
                   '@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: backend
             trafficDirection: OUTBOUND
 `,
@@ -127,6 +129,8 @@ var _ = Describe("HttpAccessLogConfigurer", func() {
                       path: /tmp/log
                   httpFilters:
                   - name: envoy.filters.http.router
+                    typedConfig:
+                      '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                   statPrefix: backend
             name: outbound:127.0.0.1:27070
             trafficDirection: OUTBOUND`,
@@ -179,6 +183,8 @@ var _ = Describe("HttpAccessLogConfigurer", func() {
                         transportApiVersion: V3
                   httpFilters:
                   - name: envoy.filters.http.router
+                    typedConfig:
+                      '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                   statPrefix: backend
             name: outbound:127.0.0.1:27070
             trafficDirection: OUTBOUND`,
diff --git a/pkg/xds/envoy/listeners/v3/http_connection_manager_configurer_test.go b/pkg/xds/envoy/listeners/v3/http_connection_manager_configurer_test.go
index 00d441e3e..4676c4c14 100644
--- a/pkg/xds/envoy/listeners/v3/http_connection_manager_configurer_test.go
+++ b/pkg/xds/envoy/listeners/v3/http_connection_manager_configurer_test.go
@@ -62,6 +62,8 @@ var _ = Describe("HttpConnectionManagerConfigurer", func() {
                   statPrefix: localhost_8080
                   httpFilters:
                   - name: envoy.filters.http.router
+                    typedConfig:
+                      '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
 `,
 		}),
 	)
diff --git a/pkg/xds/envoy/listeners/v3/http_inbound_routes_configurer_test.go b/pkg/xds/envoy/listeners/v3/http_inbound_routes_configurer_test.go
index 7cf9a3541..47819f537 100644
--- a/pkg/xds/envoy/listeners/v3/http_inbound_routes_configurer_test.go
+++ b/pkg/xds/envoy/listeners/v3/http_inbound_routes_configurer_test.go
@@ -99,6 +99,8 @@ var _ = Describe("HttpInboundRouteConfigurer", func() {
                     uri: true
                   httpFilters:
                   - name: envoy.filters.http.router
+                    typedConfig:
+                      '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                   routeConfig:
                     name: inbound:backend
                     validateClusters: false
@@ -161,6 +163,8 @@ var _ = Describe("HttpInboundRouteConfigurer", func() {
                     uri: true
                   httpFilters:
                   - name: envoy.filters.http.router
+                    typedConfig:
+                      '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                   routeConfig:
                     name: inbound:backend
                     validateClusters: false
@@ -253,6 +257,8 @@ var _ = Describe("HttpInboundRouteConfigurer", func() {
                     uri: true
                   httpFilters:
                   - name: envoy.filters.http.router
+                    typedConfig:
+                      '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                   routeConfig:
                     name: inbound:backend
                     validateClusters: false
diff --git a/pkg/xds/envoy/listeners/v3/http_outbound_route_configurer_test.go b/pkg/xds/envoy/listeners/v3/http_outbound_route_configurer_test.go
index b74164887..36f21ca0c 100644
--- a/pkg/xds/envoy/listeners/v3/http_outbound_route_configurer_test.go
+++ b/pkg/xds/envoy/listeners/v3/http_outbound_route_configurer_test.go
@@ -83,6 +83,8 @@ var _ = Describe("HttpOutboundRouteConfigurer", func() {
                   '@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
                   routeConfig:
                     name: outbound:backend
                     validateClusters: false
@@ -214,6 +216,8 @@ var _ = Describe("HttpOutboundRouteConfigurer", func() {
                   '@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
                   routeConfig:
                     name: outbound:backend
                     requestHeadersToAdd:
diff --git a/pkg/xds/envoy/listeners/v3/http_route_configurer_test.go b/pkg/xds/envoy/listeners/v3/http_route_configurer_test.go
index c1dbb612f..635684c1f 100644
--- a/pkg/xds/envoy/listeners/v3/http_route_configurer_test.go
+++ b/pkg/xds/envoy/listeners/v3/http_route_configurer_test.go
@@ -38,6 +38,8 @@ var _ = Describe("HttpDynamicRouteConfigurer", func() {
             '@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
             rds:
               configSource:
                 ads: {}
diff --git a/pkg/xds/envoy/listeners/v3/rate_limit_configurer_test.go b/pkg/xds/envoy/listeners/v3/rate_limit_configurer_test.go
index 42f335a51..51448216c 100644
--- a/pkg/xds/envoy/listeners/v3/rate_limit_configurer_test.go
+++ b/pkg/xds/envoy/listeners/v3/rate_limit_configurer_test.go
@@ -63,6 +63,8 @@ var _ = Describe("RateLimitConfigurer", func() {
                     '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
                     statPrefix: rate_limit
                 - name: envoy.filters.http.router
+                  typedConfig:
+                    '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                 statPrefix: stats`,
 		}),
 	)
diff --git a/pkg/xds/envoy/listeners/v3/retry_configurer_test.go b/pkg/xds/envoy/listeners/v3/retry_configurer_test.go
index fa91583f3..6cbbce6f7 100644
--- a/pkg/xds/envoy/listeners/v3/retry_configurer_test.go
+++ b/pkg/xds/envoy/listeners/v3/retry_configurer_test.go
@@ -95,6 +95,8 @@ var _ = Describe("RetryConfigurer", func() {
                   '@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
                   routeConfig:
                     name: outbound:backend
                     validateClusters: false
@@ -172,6 +174,8 @@ var _ = Describe("RetryConfigurer", func() {
                   '@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
                   routeConfig:
                     name: outbound:backend
                     validateClusters: false
@@ -245,6 +249,8 @@ var _ = Describe("RetryConfigurer", func() {
                   '@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
                   routeConfig:
                     name: outbound:backend
                     validateClusters: false
@@ -320,6 +326,8 @@ var _ = Describe("RetryConfigurer", func() {
                   '@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
                   routeConfig:
                     name: outbound:backend
                     validateClusters: false
diff --git a/pkg/xds/envoy/listeners/v3/static_endpoints_configurer_test.go b/pkg/xds/envoy/listeners/v3/static_endpoints_configurer_test.go
index ee531a0ca..efc91d808 100644
--- a/pkg/xds/envoy/listeners/v3/static_endpoints_configurer_test.go
+++ b/pkg/xds/envoy/listeners/v3/static_endpoints_configurer_test.go
@@ -67,6 +67,8 @@ var _ = Describe("StaticEndpointsConfigurer", func() {
                   '@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
                   routeConfig:
                     validateClusters: false
                     virtualHosts:
diff --git a/pkg/xds/envoy/listeners/v3/tracing_configurer_test.go b/pkg/xds/envoy/listeners/v3/tracing_configurer_test.go
index fdb2b3155..50e2cbb36 100644
--- a/pkg/xds/envoy/listeners/v3/tracing_configurer_test.go
+++ b/pkg/xds/envoy/listeners/v3/tracing_configurer_test.go
@@ -58,6 +58,8 @@ var _ = Describe("TracingConfigurer", func() {
                   '@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: localhost_8080
                   tracing:
                     overallSampling:
@@ -94,6 +96,8 @@ var _ = Describe("TracingConfigurer", func() {
                   '@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: localhost_8080
                   tracing:
                     provider:
@@ -129,6 +133,8 @@ var _ = Describe("TracingConfigurer", func() {
               '@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: localhost_8080
               tracing:
                 provider:
@@ -159,6 +165,8 @@ var _ = Describe("TracingConfigurer", func() {
                   statPrefix: localhost_8080
                   httpFilters:
                   - name: envoy.filters.http.router
+                    typedConfig:
+                      '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
 `,
 		}),
 	)
diff --git a/pkg/xds/generator/egress/testdata/01.externalservice-only.golden.yaml b/pkg/xds/generator/egress/testdata/01.externalservice-only.golden.yaml
index dddcb65e0..1e7a2e433 100644
--- a/pkg/xds/generator/egress/testdata/01.externalservice-only.golden.yaml
+++ b/pkg/xds/generator/egress/testdata/01.externalservice-only.golden.yaml
@@ -60,6 +60,8 @@ resources:
           '@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
           routeConfig:
             name: outbound:externalservice-1
             validateClusters: false
diff --git a/pkg/xds/generator/egress/testdata/03.mixed-services.golden.yaml b/pkg/xds/generator/egress/testdata/03.mixed-services.golden.yaml
index 1750eb986..ff2231336 100644
--- a/pkg/xds/generator/egress/testdata/03.mixed-services.golden.yaml
+++ b/pkg/xds/generator/egress/testdata/03.mixed-services.golden.yaml
@@ -212,6 +212,8 @@ resources:
           '@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
           routeConfig:
             name: outbound:externalservice-1
             validateClusters: false
@@ -267,6 +269,8 @@ resources:
           '@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
           routeConfig:
             name: outbound:externalservice-2
             validateClusters: false
diff --git a/pkg/xds/generator/egress/testdata/04.mixed-services-custom-trafficroute.golden.yaml b/pkg/xds/generator/egress/testdata/04.mixed-services-custom-trafficroute.golden.yaml
index 3e5eeabf4..c810304fb 100644
--- a/pkg/xds/generator/egress/testdata/04.mixed-services-custom-trafficroute.golden.yaml
+++ b/pkg/xds/generator/egress/testdata/04.mixed-services-custom-trafficroute.golden.yaml
@@ -172,6 +172,8 @@ resources:
           '@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
           routeConfig:
             name: outbound:externalservice-2
             validateClusters: false
diff --git a/pkg/xds/generator/egress/testdata/05.mixed-services-with-custom-trafficpermissions.golden.yaml b/pkg/xds/generator/egress/testdata/05.mixed-services-with-custom-trafficpermissions.golden.yaml
index 6948335b3..69231a94a 100644
--- a/pkg/xds/generator/egress/testdata/05.mixed-services-with-custom-trafficpermissions.golden.yaml
+++ b/pkg/xds/generator/egress/testdata/05.mixed-services-with-custom-trafficpermissions.golden.yaml
@@ -214,6 +214,8 @@ resources:
           '@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
           routeConfig:
             name: outbound:externalservice-1
             validateClusters: false
@@ -263,6 +265,8 @@ resources:
           '@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
           routeConfig:
             name: outbound:externalservice-2
             validateClusters: false
diff --git a/pkg/xds/generator/egress/testdata/06.mixed-services-with-external-in-other-zone.golden.yaml b/pkg/xds/generator/egress/testdata/06.mixed-services-with-external-in-other-zone.golden.yaml
index a85c32cea..c562c6ac9 100644
--- a/pkg/xds/generator/egress/testdata/06.mixed-services-with-external-in-other-zone.golden.yaml
+++ b/pkg/xds/generator/egress/testdata/06.mixed-services-with-external-in-other-zone.golden.yaml
@@ -217,6 +217,8 @@ resources:
           '@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
           routeConfig:
             name: outbound:externalservice-1
             validateClusters: false
diff --git a/pkg/xds/generator/prometheus_endpoint_generator_test.go b/pkg/xds/generator/prometheus_endpoint_generator_test.go
index 95d1f4c76..946e671d8 100644
--- a/pkg/xds/generator/prometheus_endpoint_generator_test.go
+++ b/pkg/xds/generator/prometheus_endpoint_generator_test.go
@@ -7,6 +7,7 @@ import (
 	. "github.com/onsi/gomega"
 
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
+	"github.com/kumahq/kuma/pkg/core"
 	core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
 	core_xds "github.com/kumahq/kuma/pkg/core/xds"
 	. "github.com/kumahq/kuma/pkg/test/matchers"
@@ -20,6 +21,10 @@ import (
 
 var _ = Describe("PrometheusEndpointGenerator", func() {
 
+	core.TempDir = func() string {
+		return "/tmp"
+	}
+
 	type testCase struct {
 		ctx      xds_context.Context
 		proxy    *core_xds.Proxy
diff --git a/pkg/xds/generator/testdata/admin/01.envoy-config.golden.yaml b/pkg/xds/generator/testdata/admin/01.envoy-config.golden.yaml
index f6a252fe4..45ac07dc5 100644
--- a/pkg/xds/generator/testdata/admin/01.envoy-config.golden.yaml
+++ b/pkg/xds/generator/testdata/admin/01.envoy-config.golden.yaml
@@ -30,6 +30,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
@@ -51,6 +53,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
diff --git a/pkg/xds/generator/testdata/inbound-proxy/3-envoy-config.golden.yaml b/pkg/xds/generator/testdata/inbound-proxy/3-envoy-config.golden.yaml
index 83fbf5415..92d2a67e1 100644
--- a/pkg/xds/generator/testdata/inbound-proxy/3-envoy-config.golden.yaml
+++ b/pkg/xds/generator/testdata/inbound-proxy/3-envoy-config.golden.yaml
@@ -127,6 +127,8 @@ resources:
               '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
               statPrefix: rate_limit
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend1
             requestHeadersToRemove:
@@ -298,6 +300,8 @@ resources:
           forwardClientCertDetails: SANITIZE_SET
           httpFilters:
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend3
             requestHeadersToRemove:
diff --git a/pkg/xds/generator/testdata/inbound-proxy/4-envoy-config.golden.yaml b/pkg/xds/generator/testdata/inbound-proxy/4-envoy-config.golden.yaml
index ea693efa0..fd12762a0 100644
--- a/pkg/xds/generator/testdata/inbound-proxy/4-envoy-config.golden.yaml
+++ b/pkg/xds/generator/testdata/inbound-proxy/4-envoy-config.golden.yaml
@@ -129,6 +129,8 @@ resources:
               '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
               statPrefix: rate_limit
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend1
             requestHeadersToRemove:
@@ -302,6 +304,8 @@ resources:
           forwardClientCertDetails: SANITIZE_SET
           httpFilters:
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend3
             requestHeadersToRemove:
diff --git a/pkg/xds/generator/testdata/inbound-proxy/5-envoy-config.golden.yaml b/pkg/xds/generator/testdata/inbound-proxy/5-envoy-config.golden.yaml
index 3934da340..0942476bf 100644
--- a/pkg/xds/generator/testdata/inbound-proxy/5-envoy-config.golden.yaml
+++ b/pkg/xds/generator/testdata/inbound-proxy/5-envoy-config.golden.yaml
@@ -163,6 +163,8 @@ resources:
               '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
               statPrefix: rate_limit
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend1
             requestHeadersToRemove:
@@ -283,6 +285,8 @@ resources:
               '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
               statPrefix: rate_limit
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend1
             requestHeadersToRemove:
@@ -405,6 +409,8 @@ resources:
               '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
               statPrefix: rate_limit
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend1
             requestHeadersToRemove:
@@ -616,6 +622,8 @@ resources:
           forwardClientCertDetails: SANITIZE_SET
           httpFilters:
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend3
             requestHeadersToRemove:
@@ -648,6 +656,8 @@ resources:
           forwardClientCertDetails: SANITIZE_SET
           httpFilters:
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend3
             requestHeadersToRemove:
@@ -682,6 +692,8 @@ resources:
           forwardClientCertDetails: SANITIZE_SET
           httpFilters:
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend3
             requestHeadersToRemove:
diff --git a/pkg/xds/generator/testdata/inbound-proxy/6-envoy-config.golden.yaml b/pkg/xds/generator/testdata/inbound-proxy/6-envoy-config.golden.yaml
index 05fbda78b..49c935d64 100644
--- a/pkg/xds/generator/testdata/inbound-proxy/6-envoy-config.golden.yaml
+++ b/pkg/xds/generator/testdata/inbound-proxy/6-envoy-config.golden.yaml
@@ -165,6 +165,8 @@ resources:
               '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
               statPrefix: rate_limit
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend1
             requestHeadersToRemove:
@@ -285,6 +287,8 @@ resources:
               '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
               statPrefix: rate_limit
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend1
             requestHeadersToRemove:
@@ -407,6 +411,8 @@ resources:
               '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
               statPrefix: rate_limit
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend1
             requestHeadersToRemove:
@@ -620,6 +626,8 @@ resources:
           forwardClientCertDetails: SANITIZE_SET
           httpFilters:
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend3
             requestHeadersToRemove:
@@ -652,6 +660,8 @@ resources:
           forwardClientCertDetails: SANITIZE_SET
           httpFilters:
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend3
             requestHeadersToRemove:
@@ -686,6 +696,8 @@ resources:
           forwardClientCertDetails: SANITIZE_SET
           httpFilters:
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend3
             requestHeadersToRemove:
diff --git a/pkg/xds/generator/testdata/outbound-proxy/03.envoy.golden.yaml b/pkg/xds/generator/testdata/outbound-proxy/03.envoy.golden.yaml
index b2a26dc38..2f78ca78c 100644
--- a/pkg/xds/generator/testdata/outbound-proxy/03.envoy.golden.yaml
+++ b/pkg/xds/generator/testdata/outbound-proxy/03.envoy.golden.yaml
@@ -329,6 +329,8 @@ resources:
               path: /var/log
           httpFilters:
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: outbound:api-http
             requestHeadersToAdd:
@@ -398,6 +400,8 @@ resources:
           '@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
           routeConfig:
             name: outbound:api-http2
             requestHeadersToAdd:
@@ -441,6 +445,8 @@ resources:
               emitFilterState: true
               statsForAllMethods: true
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: outbound:api-grpc
             requestHeadersToAdd:
diff --git a/pkg/xds/generator/testdata/outbound-proxy/04.envoy.golden.yaml b/pkg/xds/generator/testdata/outbound-proxy/04.envoy.golden.yaml
index d67821499..29e0aea04 100644
--- a/pkg/xds/generator/testdata/outbound-proxy/04.envoy.golden.yaml
+++ b/pkg/xds/generator/testdata/outbound-proxy/04.envoy.golden.yaml
@@ -367,6 +367,8 @@ resources:
               path: /var/log
           httpFilters:
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: outbound:api-http
             requestHeadersToAdd:
diff --git a/pkg/xds/generator/testdata/outbound-proxy/05.envoy.golden.yaml b/pkg/xds/generator/testdata/outbound-proxy/05.envoy.golden.yaml
index 74c353feb..e9ee8af42 100644
--- a/pkg/xds/generator/testdata/outbound-proxy/05.envoy.golden.yaml
+++ b/pkg/xds/generator/testdata/outbound-proxy/05.envoy.golden.yaml
@@ -43,6 +43,8 @@ resources:
           '@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
           routeConfig:
             name: outbound:es
             requestHeadersToAdd:
diff --git a/pkg/xds/generator/testdata/outbound-proxy/06.envoy.golden.yaml b/pkg/xds/generator/testdata/outbound-proxy/06.envoy.golden.yaml
index 32e0dc521..1ee3cbc20 100644
--- a/pkg/xds/generator/testdata/outbound-proxy/06.envoy.golden.yaml
+++ b/pkg/xds/generator/testdata/outbound-proxy/06.envoy.golden.yaml
@@ -43,6 +43,8 @@ resources:
           '@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
           routeConfig:
             name: outbound:es2
             requestHeadersToAdd:
diff --git a/pkg/xds/generator/testdata/outbound-proxy/08.envoy.golden.yaml b/pkg/xds/generator/testdata/outbound-proxy/08.envoy.golden.yaml
index 1fb851226..e4413b0c5 100644
--- a/pkg/xds/generator/testdata/outbound-proxy/08.envoy.golden.yaml
+++ b/pkg/xds/generator/testdata/outbound-proxy/08.envoy.golden.yaml
@@ -72,6 +72,8 @@ resources:
           '@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
           routeConfig:
             name: outbound:es2
             requestHeadersToAdd:
@@ -118,6 +120,8 @@ resources:
           '@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
           routeConfig:
             name: outbound:es2
             requestHeadersToAdd:
@@ -164,6 +168,8 @@ resources:
           '@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
           routeConfig:
             name: outbound:es2
             requestHeadersToAdd:
diff --git a/pkg/xds/generator/testdata/probe/01.envoy.golden.yaml b/pkg/xds/generator/testdata/probe/01.envoy.golden.yaml
index 8332e0205..2acc50404 100644
--- a/pkg/xds/generator/testdata/probe/01.envoy.golden.yaml
+++ b/pkg/xds/generator/testdata/probe/01.envoy.golden.yaml
@@ -13,6 +13,8 @@ resources:
           '@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
           routeConfig:
             virtualHosts:
             - domains:
diff --git a/pkg/xds/generator/testdata/probe/03.envoy.golden.yaml b/pkg/xds/generator/testdata/probe/03.envoy.golden.yaml
index b442afb2b..d4bded219 100644
--- a/pkg/xds/generator/testdata/probe/03.envoy.golden.yaml
+++ b/pkg/xds/generator/testdata/probe/03.envoy.golden.yaml
@@ -13,6 +13,8 @@ resources:
           '@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
           routeConfig:
             virtualHosts:
             - domains:
diff --git a/pkg/xds/generator/testdata/probe/04.envoy.golden.yaml b/pkg/xds/generator/testdata/probe/04.envoy.golden.yaml
index 8332e0205..2acc50404 100644
--- a/pkg/xds/generator/testdata/probe/04.envoy.golden.yaml
+++ b/pkg/xds/generator/testdata/probe/04.envoy.golden.yaml
@@ -13,6 +13,8 @@ resources:
           '@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
           routeConfig:
             virtualHosts:
             - domains:
diff --git a/pkg/xds/generator/testdata/profile-source/1-envoy-config.golden.yaml b/pkg/xds/generator/testdata/profile-source/1-envoy-config.golden.yaml
index 815a76141..9b236843c 100644
--- a/pkg/xds/generator/testdata/profile-source/1-envoy-config.golden.yaml
+++ b/pkg/xds/generator/testdata/profile-source/1-envoy-config.golden.yaml
@@ -202,6 +202,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
@@ -223,6 +225,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
diff --git a/pkg/xds/generator/testdata/profile-source/2-envoy-config.golden.yaml b/pkg/xds/generator/testdata/profile-source/2-envoy-config.golden.yaml
index 87006afaa..00f23c3a6 100644
--- a/pkg/xds/generator/testdata/profile-source/2-envoy-config.golden.yaml
+++ b/pkg/xds/generator/testdata/profile-source/2-envoy-config.golden.yaml
@@ -241,6 +241,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
@@ -262,6 +264,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
diff --git a/pkg/xds/generator/testdata/profile-source/3-envoy-config.golden.yaml b/pkg/xds/generator/testdata/profile-source/3-envoy-config.golden.yaml
index 650bff220..41518ee61 100644
--- a/pkg/xds/generator/testdata/profile-source/3-envoy-config.golden.yaml
+++ b/pkg/xds/generator/testdata/profile-source/3-envoy-config.golden.yaml
@@ -177,6 +177,8 @@ resources:
           forwardClientCertDetails: SANITIZE_SET
           httpFilters:
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend
             requestHeadersToRemove:
@@ -237,6 +239,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
@@ -258,6 +262,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
@@ -314,6 +320,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
@@ -338,6 +346,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
diff --git a/pkg/xds/generator/testdata/profile-source/4-envoy-config.golden.yaml b/pkg/xds/generator/testdata/profile-source/4-envoy-config.golden.yaml
index 1d202b140..fd67e6658 100644
--- a/pkg/xds/generator/testdata/profile-source/4-envoy-config.golden.yaml
+++ b/pkg/xds/generator/testdata/profile-source/4-envoy-config.golden.yaml
@@ -198,6 +198,8 @@ resources:
           forwardClientCertDetails: SANITIZE_SET
           httpFilters:
           - name: envoy.filters.http.router
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
           routeConfig:
             name: inbound:backend
             requestHeadersToRemove:
@@ -276,6 +278,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
@@ -297,6 +301,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
@@ -353,6 +359,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
@@ -377,6 +385,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
diff --git a/pkg/xds/generator/testdata/prometheus-endpoint/custom.envoy-config.golden.yaml b/pkg/xds/generator/testdata/prometheus-endpoint/custom.envoy-config.golden.yaml
index b874df912..5c3ef4ad7 100644
--- a/pkg/xds/generator/testdata/prometheus-endpoint/custom.envoy-config.golden.yaml
+++ b/pkg/xds/generator/testdata/prometheus-endpoint/custom.envoy-config.golden.yaml
@@ -29,6 +29,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
diff --git a/pkg/xds/generator/testdata/prometheus-endpoint/default-mtls.envoy-config.golden.yaml b/pkg/xds/generator/testdata/prometheus-endpoint/default-mtls.envoy-config.golden.yaml
index 15fd6cb64..b66d6b27f 100644
--- a/pkg/xds/generator/testdata/prometheus-endpoint/default-mtls.envoy-config.golden.yaml
+++ b/pkg/xds/generator/testdata/prometheus-endpoint/default-mtls.envoy-config.golden.yaml
@@ -33,6 +33,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
@@ -57,6 +59,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
diff --git a/pkg/xds/generator/testdata/prometheus-endpoint/default-without-hijacker.envoy-config.golden.yaml b/pkg/xds/generator/testdata/prometheus-endpoint/default-without-hijacker.envoy-config.golden.yaml
index 3b247d2e4..06be56ae9 100644
--- a/pkg/xds/generator/testdata/prometheus-endpoint/default-without-hijacker.envoy-config.golden.yaml
+++ b/pkg/xds/generator/testdata/prometheus-endpoint/default-without-hijacker.envoy-config.golden.yaml
@@ -29,6 +29,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
diff --git a/pkg/xds/generator/testdata/prometheus-endpoint/default.envoy-config.golden.yaml b/pkg/xds/generator/testdata/prometheus-endpoint/default.envoy-config.golden.yaml
index 3b247d2e4..06be56ae9 100644
--- a/pkg/xds/generator/testdata/prometheus-endpoint/default.envoy-config.golden.yaml
+++ b/pkg/xds/generator/testdata/prometheus-endpoint/default.envoy-config.golden.yaml
@@ -29,6 +29,8 @@ resources:
           '@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
           routeConfig:
             validateClusters: false
             virtualHosts:
diff --git a/pkg/xds/topology/outbound_test.go b/pkg/xds/topology/outbound_test.go
index e7edb1305..ad0fffdc3 100644
--- a/pkg/xds/topology/outbound_test.go
+++ b/pkg/xds/topology/outbound_test.go
@@ -338,18 +338,10 @@ var _ = Describe("TrafficRoute", func() {
 								AdvertisedAddress: "192.168.0.100",
 								AdvertisedPort:    12345,
 							},
-							AvailableServices: []*mesh_proto.ZoneIngress_AvailableService{
-								{
-									Instances: 2,
-									Mesh:      defaultMeshName,
-									Tags:      map[string]string{mesh_proto.ServiceTag: "redis", "version": "v2", mesh_proto.ZoneTag: "eu"},
-								},
-								{
-									Instances: 3,
-									Mesh:      defaultMeshName,
-									Tags:      map[string]string{mesh_proto.ServiceTag: "redis", "version": "v3"},
-								},
-							},
+							// when AvailableServices are not computed for the instance of the ingress behind the same
+							// load balancer (advertised address + port), available services from an instance that has them
+							// is preferred.
+							AvailableServices: []*mesh_proto.ZoneIngress_AvailableService{},
 						},
 					},
 					{
diff --git a/test/e2e/healthcheck/hybrid/e2e_suite_test.go b/test/e2e/healthcheck/hybrid/e2e_suite_test.go
deleted file mode 100644
index 20435e0e8..000000000
--- a/test/e2e/healthcheck/hybrid/e2e_suite_test.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package hybrid_test
-
-import (
-	"testing"
-
-	. "github.com/onsi/ginkgo/v2"
-
-	"github.com/kumahq/kuma/pkg/test"
-	"github.com/kumahq/kuma/test/e2e/healthcheck/hybrid"
-)
-
-func TestE2E(t *testing.T) {
-	test.RunSpecs(t, "E2E Health Check Hybrid Suite")
-}
-
-var _ = Describe("Test application HealthCheck on Kubernetes/Universal", hybrid.ApplicationHealthCheckOnKubernetesUniversal)
diff --git a/test/e2e/healthcheck/hybrid/healthcheck_hybrid.go b/test/e2e/healthcheck/hybrid/healthcheck_hybrid.go
deleted file mode 100644
index 416d89fa4..000000000
--- a/test/e2e/healthcheck/hybrid/healthcheck_hybrid.go
+++ /dev/null
@@ -1,158 +0,0 @@
-package hybrid
-
-import (
-	"fmt"
-	"strings"
-	"time"
-
-	"github.com/gruntwork-io/terratest/modules/k8s"
-	"github.com/gruntwork-io/terratest/modules/retry"
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	"github.com/pkg/errors"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
-	"github.com/kumahq/kuma/pkg/config/core"
-	. "github.com/kumahq/kuma/test/framework"
-)
-
-func ApplicationHealthCheckOnKubernetesUniversal() {
-	meshMTLSOn := func(mesh string) string {
-		return fmt.Sprintf(`
-apiVersion: kuma.io/v1alpha1
-kind: Mesh
-metadata:
-  name: %s
-spec:
-  mtls:
-    enabledBackend: ca-1
-    backends:
-    - name: ca-1
-      type: builtin
-`, mesh)
-	}
-
-	var globalK8s, zoneK8s, zoneUniversal Cluster
-
-	BeforeEach(func() {
-		k8sClusters, err := NewK8sClusters([]string{Kuma1, Kuma2}, Silent)
-		Expect(err).ToNot(HaveOccurred())
-
-		universalClusters, err := NewUniversalClusters([]string{Kuma3}, Silent)
-		Expect(err).ToNot(HaveOccurred())
-
-		globalK8s = k8sClusters.GetCluster(Kuma1)
-		err = NewClusterSetup().
-			Install(Kuma(core.Global)).
-			Install(YamlK8s(meshMTLSOn("default"))).
-			Setup(globalK8s)
-		Expect(err).ToNot(HaveOccurred())
-
-		zoneK8s = k8sClusters.GetCluster(Kuma2)
-		err = NewClusterSetup().
-			Install(Kuma(core.Zone,
-				WithIngress(),
-				WithGlobalAddress(globalK8s.GetKuma().GetKDSServerAddress()),
-			)).
-			Install(NamespaceWithSidecarInjection(TestNamespace)).
-			Install(DemoClientK8s("default", TestNamespace)).
-			Setup(zoneK8s)
-		Expect(err).ToNot(HaveOccurred())
-
-		NewUniversalCluster(NewTestingT(), Kuma3, Silent)
-		zoneUniversal = universalClusters.GetCluster(Kuma3)
-
-		err = NewClusterSetup().
-			Install(Kuma(core.Zone,
-				WithGlobalAddress(globalK8s.GetKuma().GetKDSServerAddress()),
-			)).
-			Install(TestServerUniversal("test-server-1", "default",
-				WithArgs([]string{"echo", "--instance", "dp-universal-1"}),
-				WithProtocol("tcp"))).
-			Install(TestServerUniversal("test-server-2", "default",
-				WithArgs([]string{"echo", "--instance", "dp-universal-2"}),
-				WithProtocol("tcp"),
-				ProxyOnly(),
-				ServiceProbe())).
-			Install(TestServerUniversal("test-server-3", "default",
-				WithArgs([]string{"echo", "--instance", "dp-universal-3"}),
-				WithProtocol("tcp"))).
-			Install(IngressUniversal(globalK8s.GetKuma().GenerateZoneIngressToken)).
-			Setup(zoneUniversal)
-		Expect(err).ToNot(HaveOccurred())
-	})
-
-	E2EAfterEach(func() {
-		Expect(zoneK8s.DeleteNamespace(TestNamespace)).To(Succeed())
-		err := zoneK8s.DeleteKuma()
-		Expect(err).ToNot(HaveOccurred())
-		err = zoneK8s.DismissCluster()
-		Expect(err).ToNot(HaveOccurred())
-
-		err = zoneUniversal.DeleteKuma()
-		Expect(err).ToNot(HaveOccurred())
-		err = zoneUniversal.DismissCluster()
-		Expect(err).ToNot(HaveOccurred())
-
-		err = globalK8s.DeleteKuma()
-		Expect(err).ToNot(HaveOccurred())
-		err = globalK8s.DismissCluster()
-		Expect(err).ToNot(HaveOccurred())
-	})
-
-	It("should not load balance requests to unhealthy instance", func() {
-		pods, err := k8s.ListPodsE(zoneK8s.GetTesting(), zoneK8s.GetKubectlOptions(TestNamespace), metav1.ListOptions{
-			LabelSelector: fmt.Sprintf("app=%s", "demo-client"),
-		})
-		Expect(err).ToNot(HaveOccurred())
-		Expect(pods).To(HaveLen(1))
-
-		cmd := []string{"curl", "-v", "-m", "3", "--fail", "test-server.mesh"}
-
-		instances := []string{"dp-universal-1", "dp-universal-3"}
-		instanceSet := map[string]bool{}
-
-		_, err = retry.DoWithRetryE(zoneK8s.GetTesting(), fmt.Sprintf("kubectl exec %s -- %s", pods[0].GetName(), strings.Join(cmd, " ")),
-			100, 500*time.Millisecond, func() (string, error) {
-				stdout, _, err := zoneK8s.Exec(TestNamespace, pods[0].GetName(), "demo-client", cmd...)
-				if err != nil {
-					return "", err
-				}
-				for _, instance := range instances {
-					if strings.Contains(stdout, instance) {
-						instanceSet[instance] = true
-					}
-				}
-				if len(instanceSet) != len(instances) {
-					return "", errors.Errorf("checked %d/%d instances", len(instanceSet), len(instances))
-				}
-				return "", nil
-			},
-		)
-		Expect(err).ToNot(HaveOccurred())
-
-		var counter1, counter2, counter3 int
-		const numOfRequest = 100
-
-		for i := 0; i < numOfRequest; i++ {
-			var stdout string
-
-			stdout, _, err = zoneK8s.Exec(TestNamespace, pods[0].GetName(), "demo-client", cmd...)
-			Expect(err).ToNot(HaveOccurred())
-
-			switch {
-			case strings.Contains(stdout, "dp-universal-1"):
-				counter1++
-			case strings.Contains(stdout, "dp-universal-2"):
-				counter2++
-			case strings.Contains(stdout, "dp-universal-3"):
-				counter3++
-			}
-		}
-
-		Expect(counter2).To(Equal(0))
-		Expect(counter1 > 0).To(BeTrue())
-		Expect(counter3 > 0).To(BeTrue())
-		Expect(counter1 + counter3).To(Equal(numOfRequest))
-	})
-}
diff --git a/test/e2e/healthcheck/universal/e2e_suite_test.go b/test/e2e/healthcheck/universal/e2e_suite_test.go
deleted file mode 100644
index 6d1ebb28a..000000000
--- a/test/e2e/healthcheck/universal/e2e_suite_test.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package universal_test
-
-import (
-	"testing"
-
-	. "github.com/onsi/ginkgo/v2"
-
-	"github.com/kumahq/kuma/pkg/test"
-	"github.com/kumahq/kuma/test/e2e/healthcheck/universal"
-)
-
-func TestE2E(t *testing.T) {
-	test.RunSpecs(t, "E2E Health Check Universal Suite")
-}
-
-var _ = Describe("Test Health Check TCP policy on Universal", universal.PolicyTCP)
-var _ = Describe("Test Health Check HTTP policy on Universal", universal.PolicyHTTP)
diff --git a/test/e2e/healthcheck/universal/policy_http.go b/test/e2e/healthcheck/universal/policy_http.go
deleted file mode 100644
index 91b4dad51..000000000
--- a/test/e2e/healthcheck/universal/policy_http.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package universal
-
-import (
-	"fmt"
-	"strings"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-
-	config_core "github.com/kumahq/kuma/pkg/config/core"
-	. "github.com/kumahq/kuma/test/framework"
-)
-
-func PolicyHTTP() {
-	healthCheck := func(method, status string) string {
-		return fmt.Sprintf(`
-type: HealthCheck
-name: everything-to-backend
-mesh: default
-sources:
-- match:
-    kuma.io/service: '*'
-destinations:
-- match:
-    kuma.io/service: test-server
-conf:
-  interval: 10s
-  timeout: 2s
-  unhealthyThreshold: 3
-  healthyThreshold: 1
-  failTrafficOnPanic: true
-  noTrafficInterval: 1s
-  healthyPanicThreshold: 0
-  reuse_connection: true
-  http: 
-    path: /%s
-    expectedStatuses: 
-    - %s`, method, status)
-	}
-
-	var cluster Cluster
-
-	BeforeEach(func() {
-		cluster = NewUniversalCluster(NewTestingT(), Kuma3, Verbose)
-
-		err := NewClusterSetup().
-			Install(Kuma(config_core.Standalone)).
-			Install(YamlUniversal(healthCheck("health", "200"))).
-			Install(DemoClientUniversal("dp-demo-client", "default",
-				WithTransparentProxy(true)),
-			).
-			Install(TestServerUniversal("test-server", "default", WithArgs([]string{"health-check", "http"}))).
-			Setup(cluster)
-		Expect(err).ToNot(HaveOccurred())
-	})
-
-	E2EAfterEach(func() {
-		Expect(cluster.DismissCluster()).To(Succeed())
-	})
-
-	It("should mark host as unhealthy if it doesn't reply on health checks", func() {
-		// check that test-server is healthy
-		cmd := []string{"curl", "--fail", "test-server.mesh/content"}
-		stdout, _, err := cluster.ExecWithRetries("", "", "dp-demo-client", cmd...)
-		Expect(err).ToNot(HaveOccurred())
-		Expect(stdout).To(ContainSubstring("response"))
-
-		// update HealthCheck policy to check for another status code
-		err = YamlUniversal(healthCheck("are-you-healthy", "500"))(cluster)
-		Expect(err).ToNot(HaveOccurred())
-
-		// wait cluster 'test-server' to be marked as unhealthy
-		Eventually(func() bool {
-			cmd := []string{"/bin/bash", "-c", "\"curl localhost:9901/clusters | grep test-server\""}
-			stdout, _, err := cluster.ExecWithRetries("", "", "dp-demo-client", cmd...)
-			if err != nil {
-				return false
-			}
-			return strings.Contains(stdout, "health_flags::/failed_active_hc")
-		}, "30s", "500ms").Should(BeTrue())
-
-		// check that test-server is unhealthy
-		cmd = []string{"curl", "test-server.mesh/content"}
-		stdout, _, err = cluster.ExecWithRetries("", "", "dp-demo-client", cmd...)
-		Expect(err).ToNot(HaveOccurred())
-		Expect(stdout).To(ContainSubstring("no healthy upstream"))
-	})
-}
diff --git a/test/e2e/healthcheck/universal/policy_tcp.go b/test/e2e/healthcheck/universal/policy_tcp.go
deleted file mode 100644
index 2d59fcaf2..000000000
--- a/test/e2e/healthcheck/universal/policy_tcp.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package universal
-
-import (
-	"encoding/base64"
-	"fmt"
-	"strings"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-
-	config_core "github.com/kumahq/kuma/pkg/config/core"
-	. "github.com/kumahq/kuma/test/framework"
-)
-
-func PolicyTCP() {
-	healthCheck := func(send, recv string) string {
-		sendBase64 := base64.StdEncoding.EncodeToString([]byte(send))
-		recvBase64 := base64.StdEncoding.EncodeToString([]byte(recv))
-
-		return fmt.Sprintf(`
-type: HealthCheck
-name: gateway-to-backend
-mesh: default
-sources:
-- match:
-    kuma.io/service: '*'
-destinations:
-- match:
-    kuma.io/service: test-server
-conf:
-  interval: 10s
-  timeout: 2s
-  unhealthyThreshold: 3
-  healthyThreshold: 1
-  failTrafficOnPanic: true
-  noTrafficInterval: 1s
-  healthyPanicThreshold: 0
-  reuse_connection: true
-  tcp: 
-    send: %s
-    receive:
-    - %s`, sendBase64, recvBase64)
-	}
-
-	var cluster Cluster
-
-	BeforeEach(func() {
-		cluster = NewUniversalCluster(NewTestingT(), Kuma3, Verbose)
-
-		err := NewClusterSetup().
-			Install(Kuma(config_core.Standalone)).
-			Install(YamlUniversal(healthCheck("foo", "bar"))).
-			Install(DemoClientUniversal("dp-demo-client", "default",
-				WithTransparentProxy(true)),
-			).
-			Install(TestServerUniversal("test-server", "default",
-				WithArgs([]string{"health-check", "tcp"}),
-				WithProtocol("tcp")),
-			).
-			Setup(cluster)
-		Expect(err).ToNot(HaveOccurred())
-	})
-
-	E2EAfterEach(func() {
-		Expect(cluster.DismissCluster()).To(Succeed())
-	})
-
-	It("should mark host as unhealthy if it doesn't reply on health checks", func() {
-		// check that test-server is healthy
-		cmd := []string{"/bin/bash", "-c", "\"echo request | nc test-server.mesh 80\""}
-		stdout, _, err := cluster.ExecWithRetries("", "", "dp-demo-client", cmd...)
-		Expect(err).ToNot(HaveOccurred())
-		Expect(stdout).To(ContainSubstring("response"))
-
-		// update HealthCheck policy to check for another 'recv' line
-		err = YamlUniversal(healthCheck("foo", "baz"))(cluster)
-		Expect(err).ToNot(HaveOccurred())
-
-		// wait cluster 'test-server' to be marked as unhealthy
-		Eventually(func() bool {
-			cmd := []string{"/bin/bash", "-c", "\"curl localhost:9901/clusters | grep test-server\""}
-			stdout, _, err := cluster.ExecWithRetries("", "", "dp-demo-client", cmd...)
-			if err != nil {
-				return false
-			}
-			return strings.Contains(stdout, "health_flags::/failed_active_hc")
-		}, "30s", "500ms").Should(BeTrue())
-
-		// check that test-server is unhealthy
-		cmd = []string{"/bin/bash", "-c", "\"echo request | nc test-server.mesh 80\""}
-		stdout, _, _ = cluster.ExecWithRetries("", "", "dp-demo-client", cmd...)
-
-		// there is no real attempt to setup a connection with test-server, but Envoy may return either
-		// empty response with EXIT_CODE = 0, or  'Ncat: Connection reset by peer.' with EXIT_CODE = 1
-		Expect(stdout).To(Or(BeEmpty(), ContainSubstring("Ncat: Connection reset by peer.")))
-	})
-}
diff --git a/test/e2e/inspect/inspect_k8s_multizone.go b/test/e2e/inspect/inspect_k8s_multizone.go
deleted file mode 100644
index 47c72599d..000000000
--- a/test/e2e/inspect/inspect_k8s_multizone.go
+++ /dev/null
@@ -1,109 +0,0 @@
-package inspect
-
-import (
-	"fmt"
-
-	"github.com/gruntwork-io/terratest/modules/k8s"
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	kube_core "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
-	config_core "github.com/kumahq/kuma/pkg/config/core"
-	. "github.com/kumahq/kuma/test/framework"
-)
-
-func KubernetesMultizone() {
-	var globalK8s, zoneK8s *K8sCluster
-	var zoneIngress *kube_core.Pod
-
-	meshMTLSOn := func(mesh string) string {
-		return fmt.Sprintf(`
-apiVersion: kuma.io/v1alpha1
-kind: Mesh
-metadata:
-  name: %s
-spec:
-  mtls:
-    enabledBackend: ca-1
-    backends:
-    - name: ca-1
-      type: builtin
-`, mesh)
-	}
-
-	GetPod := func(namespace, app string) *kube_core.Pod {
-		pods, err := k8s.ListPodsE(
-			zoneK8s.GetTesting(),
-			zoneK8s.GetKubectlOptions(namespace),
-			metav1.ListOptions{
-				LabelSelector: fmt.Sprintf("app=%s", app),
-			},
-		)
-		Expect(err).ToNot(HaveOccurred())
-		Expect(pods).To(HaveLen(1))
-
-		return &pods[0]
-	}
-
-	BeforeEach(func() {
-		k8sClusters, err := NewK8sClusters([]string{Kuma1, Kuma2}, Silent)
-		Expect(err).ToNot(HaveOccurred())
-
-		globalK8s = k8sClusters.GetCluster(Kuma1).(*K8sCluster)
-		zoneK8s = k8sClusters.GetCluster(Kuma2).(*K8sCluster)
-
-		err = NewClusterSetup().
-			Install(Kuma(config_core.Global)).
-			Install(YamlK8s(meshMTLSOn("default"))).
-			Setup(globalK8s)
-		Expect(err).ToNot(HaveOccurred())
-
-		err = NewClusterSetup().
-			Install(Kuma(config_core.Zone,
-				WithIngress(),
-				WithGlobalAddress(globalK8s.GetKuma().GetKDSServerAddress()),
-			)).
-			Install(NamespaceWithSidecarInjection(TestNamespace)).
-			Install(DemoClientK8s("default", TestNamespace)).
-			Setup(zoneK8s)
-		Expect(err).ToNot(HaveOccurred())
-	})
-
-	// Before each test, verify that we have the Dataplanes that we expect to need.
-	JustBeforeEach(func() {
-		Expect(globalK8s.VerifyKuma()).To(Succeed())
-		Expect(zoneK8s.VerifyKuma()).To(Succeed())
-
-		// Synchronize on the dataplanes coming up.
-		Eventually(func(g Gomega) {
-			dataplanes, err := globalK8s.GetKumactlOptions().KumactlList("dataplanes", "default")
-			g.Expect(err).ToNot(HaveOccurred())
-			// Dataplane names are generated, so we check for a partial match.
-			g.Expect(dataplanes).Should(ContainElement(ContainSubstring("demo-client")))
-		}, "60s", "1s").Should(Succeed())
-
-		zoneIngress = GetPod(Config.KumaNamespace, Config.ZoneIngressApp)
-	})
-
-	E2EAfterEach(func() {
-		Expect(zoneK8s.DeleteNamespace(TestNamespace)).To(Succeed())
-		Expect(zoneK8s.DeleteKuma()).To(Succeed())
-		Expect(zoneK8s.DismissCluster()).To(Succeed())
-
-		Expect(globalK8s.DeleteKuma()).To(Succeed())
-		Expect(globalK8s.DismissCluster()).To(Succeed())
-	})
-
-	It("should return envoy config_dump for zone ingress", func() {
-		zoneIngressName := fmt.Sprintf("%s.%s", zoneIngress.GetName(), Config.KumaNamespace)
-		Eventually(func(g Gomega) {
-			stdout, err := zoneK8s.GetKumactlOptions().RunKumactlAndGetOutput("inspect", "zoneingress", zoneIngressName, "--config-dump")
-			g.Expect(err).ToNot(HaveOccurred())
-			g.Expect(stdout).To(ContainSubstring(`"dataplane.proxyType": "ingress"`))
-
-			// filterChainMatches could be available not immediately
-			g.Expect(stdout).To(ContainSubstring(`"demo-client_kuma-test_svc{mesh=default}"`))
-		}, "30s", "1s").Should(Succeed())
-	})
-}
diff --git a/test/e2e/inspect/inspect_suite_test.go b/test/e2e/inspect/inspect_suite_test.go
index cd58f4f10..f6e71866c 100644
--- a/test/e2e/inspect/inspect_suite_test.go
+++ b/test/e2e/inspect/inspect_suite_test.go
@@ -10,7 +10,6 @@ import (
 )
 
 var _ = Describe("Test Inspect API on Kubernetes Standalone", inspect.KubernetesStandalone)
-var _ = Describe("Test Inspect API on Kubernetes Multizone", inspect.KubernetesMultizone)
 
 func TestE2EInspectAPI(t *testing.T) {
 	test.RunSpecs(t, "E2E Inspect API Suite")
diff --git a/test/e2e/membership/kubernetes/e2e_suite_test.go b/test/e2e/membership/kubernetes/e2e_suite_test.go
deleted file mode 100644
index 6045aca7e..000000000
--- a/test/e2e/membership/kubernetes/e2e_suite_test.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package kubernetes_test
-
-import (
-	"testing"
-
-	. "github.com/onsi/ginkgo/v2"
-
-	"github.com/kumahq/kuma/pkg/test"
-	"github.com/kumahq/kuma/test/e2e/membership/kubernetes"
-)
-
-func TestE2E(t *testing.T) {
-	test.RunSpecs(t, "E2E Membership Kubernetes Suite")
-}
-
-var _ = Describe("Test Kubernetes", kubernetes.MembershipKubernetes)
diff --git a/test/e2e/membership/kubernetes/membership_kubernetes.go b/test/e2e/membership/kubernetes/membership_kubernetes.go
deleted file mode 100644
index f84b062fd..000000000
--- a/test/e2e/membership/kubernetes/membership_kubernetes.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package kubernetes
-
-import (
-	"fmt"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-
-	"github.com/kumahq/kuma/pkg/config/core"
-	. "github.com/kumahq/kuma/test/framework"
-	"github.com/kumahq/kuma/test/framework/deployments/testserver"
-)
-
-func MembershipKubernetes() {
-	var cluster Cluster
-
-	const kumaTestDefaultNs = "kuma-test-default"
-	const kumaTestDemoNs = "kuma-test-demo"
-
-	meshAllowingNamespace := func(mesh, ns string) string {
-		return fmt.Sprintf(`apiVersion: kuma.io/v1alpha1
-kind: Mesh
-metadata:
-  name: %s
-spec:
-  constraints:
-    dataplaneProxy:
-      requirements:
-      - tags:
-          k8s.kuma.io/namespace: %s`, mesh, ns)
-	}
-
-	BeforeEach(func() {
-		cluster = NewK8sCluster(NewTestingT(), Kuma1, Silent)
-		err := NewClusterSetup().
-			Install(Kuma(core.Standalone)).
-			Install(NamespaceWithSidecarInjection(kumaTestDefaultNs)).
-			Install(NamespaceWithSidecarInjection(kumaTestDemoNs)).
-			Setup(cluster)
-		Expect(err).ToNot(HaveOccurred())
-	})
-
-	E2EAfterEach(func() {
-		Expect(cluster.DeleteNamespace(kumaTestDefaultNs)).To(Succeed())
-		Expect(cluster.DeleteNamespace(kumaTestDemoNs)).To(Succeed())
-		Expect(cluster.DeleteKuma()).To(Succeed())
-		Expect(cluster.DismissCluster()).To(Succeed())
-	})
-
-	It("should take into account membership when dp is connecting to the CP", func() {
-		// given
-		Expect(YamlK8s(meshAllowingNamespace("default", kumaTestDefaultNs))(cluster)).To(Succeed())
-		Expect(YamlK8s(meshAllowingNamespace("demo", kumaTestDemoNs))(cluster)).To(Succeed())
-
-		// when
-		err := testserver.Install(
-			testserver.WithNamespace(kumaTestDefaultNs),
-			testserver.WithMesh("default"),
-		)(cluster)
-
-		// then
-		Expect(err).ToNot(HaveOccurred())
-		Eventually(func() (string, error) {
-			return cluster.GetKumactlOptions().RunKumactlAndGetOutput("get", "dataplanes")
-		}, "30s", "1s").Should(ContainSubstring("test-server"))
-
-		// when trying to change mesh to demo
-		err = testserver.Install(
-			testserver.WithNamespace(kumaTestDefaultNs),
-			testserver.WithMesh("demo"),
-			testserver.WithoutWaitingToBeReady(),
-		)(cluster)
-		Expect(err).ToNot(HaveOccurred())
-
-		// then the client is not allowed to do it
-		Eventually(func() (string, error) {
-			return cluster.GetKumaCPLogs()
-		}, "30s", "1s").Should(ContainSubstring("dataplane cannot be a member of mesh"))
-		out, err := cluster.GetKumactlOptions().RunKumactlAndGetOutput("get", "dataplanes", "--mesh", "demo")
-		Expect(err).ToNot(HaveOccurred())
-		Expect(out).ToNot(ContainSubstring("test-server"))
-
-		// when a new client is deployed in demo namespace in demo mesh
-		err = testserver.Install(
-			testserver.WithNamespace(kumaTestDemoNs),
-			testserver.WithMesh("demo"),
-		)(cluster)
-
-		// then it's allowed
-		Expect(err).ToNot(HaveOccurred())
-		Eventually(func() (string, error) {
-			return cluster.GetKumactlOptions().RunKumactlAndGetOutput("get", "dataplanes", "--mesh", "demo")
-		}, "30s", "1s").Should(ContainSubstring("test-server"))
-	})
-}
diff --git a/test/e2e/membership/universal/e2e_suite_test.go b/test/e2e/membership/universal/e2e_suite_test.go
deleted file mode 100644
index 646c93123..000000000
--- a/test/e2e/membership/universal/e2e_suite_test.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package universal_test
-
-import (
-	"testing"
-
-	. "github.com/onsi/ginkgo/v2"
-
-	"github.com/kumahq/kuma/pkg/test"
-	"github.com/kumahq/kuma/test/e2e/membership/universal"
-)
-
-func TestE2E(t *testing.T) {
-	test.RunSpecs(t, "E2E Membership Universal Suite")
-}
-
-var _ = Describe("Test Universal", universal.MembershipUniversal)
diff --git a/test/e2e/membership/universal/membership_universal.go b/test/e2e/membership/universal/membership_universal.go
deleted file mode 100644
index cfbe91a7d..000000000
--- a/test/e2e/membership/universal/membership_universal.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package universal
-
-import (
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-
-	"github.com/kumahq/kuma/pkg/config/core"
-	. "github.com/kumahq/kuma/test/framework"
-)
-
-var cluster Cluster
-
-var _ = E2EBeforeSuite(func() {
-	cluster = NewUniversalCluster(NewTestingT(), Kuma3, Silent)
-
-	Expect(NewClusterSetup().
-		Install(Kuma(core.Standalone)).
-		Setup(cluster)).To(Succeed())
-
-	E2EDeferCleanup(cluster.DismissCluster)
-})
-
-func MembershipUniversal() {
-	It("should take into account membership when dp is connecting to the CP", func() {
-		mesh := `
-type: Mesh
-name: default
-constraints:
-  dataplaneProxy:
-    requirements:
-    - tags:
-        kuma.io/service: demo-client
-    restrictions:
-    - tags:
-        kuma.io/service: test-server
-`
-		Expect(YamlUniversal(mesh)(cluster)).To(Succeed())
-
-		// when demo client is trying to connect
-		err := DemoClientUniversal(AppModeDemoClient, "default")(cluster)
-		Expect(err).ToNot(HaveOccurred())
-
-		// then it's allowed
-		Eventually(func() (string, error) {
-			return cluster.GetKumactlOptions().RunKumactlAndGetOutput("get", "dataplanes")
-		}, "30s", "1s").ShouldNot(ContainSubstring("demo-client"))
-
-		// when test server is trying to connect
-		err = TestServerUniversal("test-server", "default", WithArgs([]string{"echo", "--instance", "echo-v1"}))(cluster)
-		Expect(err).ToNot(HaveOccurred())
-
-		// then it's not allowed
-		Eventually(func() (string, error) {
-			return cluster.GetKumaCPLogs()
-		}, "30s", "1s").Should(ContainSubstring("dataplane cannot be a member of mesh"))
-		dataplanes, err := cluster.GetKumactlOptions().RunKumactlAndGetOutput("get", "dataplanes")
-		Expect(err).ToNot(HaveOccurred())
-		Expect(dataplanes).ToNot(ContainSubstring("test-server"))
-	})
-}
diff --git a/test/e2e_env/kubernetes/graceful/graceful.go b/test/e2e_env/kubernetes/graceful/graceful.go
index b2b3b9109..31a6a78f4 100644
--- a/test/e2e_env/kubernetes/graceful/graceful.go
+++ b/test/e2e_env/kubernetes/graceful/graceful.go
@@ -1,9 +1,12 @@
 package graceful
 
 import (
+	"fmt"
 	"io"
 	"io/ioutil"
 	"net/http"
+	"strconv"
+	"time"
 
 	"github.com/gruntwork-io/terratest/modules/k8s"
 	. "github.com/onsi/ginkgo/v2"
@@ -30,7 +33,8 @@ func Graceful() {
 
 	// Set up a gateway to be able to send requests constantly.
 	// The alternative was to exec to the container, but this introduces a latency of getting into container for every curl
-	gateway := `
+	gatewayInstnace := func(replicas int) string {
+		return fmt.Sprintf(`
 ---
 apiVersion: kuma.io/v1alpha1
 kind: MeshGatewayInstance
@@ -40,10 +44,14 @@ metadata:
   annotations:
     kuma.io/mesh: graceful
 spec:
-  replicas: 1
+  replicas: %d
   serviceType: LoadBalancer
   tags:
     kuma.io/service: edge-gateway
+`, replicas)
+	}
+
+	gateway := `
 ---
 apiVersion: kuma.io/v1alpha1
 kind: MeshGateway
@@ -84,6 +92,10 @@ spec:
 
 	var gatewayIP string
 
+	httpClient := http.Client{
+		Timeout: 5 * time.Second,
+	}
+
 	BeforeAll(func() {
 		E2EDeferCleanup(func() {
 			Expect(env.Cluster.TriggerDeleteNamespace(namespace)).To(Succeed())
@@ -94,6 +106,7 @@ spec:
 			Install(MeshKubernetes(mesh)).
 			Install(NamespaceWithSidecarInjection(namespace)).
 			Install(YamlK8s(gateway)).
+			Install(YamlK8s(gatewayInstnace(1))).
 			Install(testserver.Install(
 				testserver.WithNamespace(namespace),
 				testserver.WithMesh(mesh),
@@ -112,10 +125,18 @@ spec:
 			g.Expect(out).ToNot(BeEmpty())
 			gatewayIP = out
 		}, "60s", "1s").Should(Succeed(), "could not get a LoadBalancer IP of the Gateway")
+
+		// remove retries to avoid covering failed request
+		err = k8s.RunKubectlE(
+			env.Cluster.GetTesting(),
+			env.Cluster.GetKubectlOptions(),
+			"delete", "retry", "retry-all-graceful",
+		)
+		Expect(err).ToNot(HaveOccurred())
 	})
 
 	requestThroughGateway := func() error {
-		resp, err := http.Get("http://" + gatewayIP + ":8080")
+		resp, err := httpClient.Get("http://" + gatewayIP + ":8080")
 		if err != nil {
 			return err
 		}
@@ -127,60 +148,65 @@ spec:
 		return err
 	}
 
-	It("should not drop a request when scaling up and down", func() {
-		// given no retries
-		err := k8s.RunKubectlE(
-			env.Cluster.GetTesting(),
-			env.Cluster.GetKubectlOptions(),
-			"delete", "retry", "retry-all-graceful",
-		)
-		Expect(err).ToNot(HaveOccurred())
+	type testCase struct {
+		deploymentName string
+		scaleFn        func(int) error
+	}
 
-		// and constant traffic between client and server
-		Eventually(requestThroughGateway, "30s", "1s").Should(Succeed())
-		var failedErr error
-		closeCh := make(chan struct{})
-		defer close(closeCh)
-		go func() {
-			for {
-				if err := requestThroughGateway(); err != nil {
-					failedErr = err
-					return
-				}
-				if channels.IsClosed(closeCh) {
-					return
+	DescribeTable("should not drop a request when scaling up and down",
+		func(given testCase) {
+			// given constant traffic between client and server
+			Eventually(requestThroughGateway, "30s", "1s").Should(Succeed())
+			var failedErr error
+			closeCh := make(chan struct{})
+			defer close(closeCh)
+			go func() {
+				for {
+					if err := requestThroughGateway(); err != nil {
+						failedErr = err
+						return
+					}
+					if channels.IsClosed(closeCh) {
+						return
+					}
 				}
-			}
-		}()
-
-		// when
-		err = k8s.RunKubectlE(
-			env.Cluster.GetTesting(),
-			env.Cluster.GetKubectlOptions(namespace),
-			"scale", "deployment", name, "--replicas", "2",
-		)
-		Expect(err).ToNot(HaveOccurred())
-
-		// then
-		Eventually(func(g Gomega) {
-			g.Expect(WaitNumPods(namespace, 2, name)(env.Cluster)).To(Succeed())
-			g.Expect(WaitPodsAvailable(namespace, name)(env.Cluster)).To(Succeed())
-		}, "30s", "1s").Should(Succeed())
-		Expect(failedErr).ToNot(HaveOccurred())
-
-		// when
-		err = k8s.RunKubectlE(
-			env.Cluster.GetTesting(),
-			env.Cluster.GetKubectlOptions(namespace),
-			"scale", "deployment", name, "--replicas", "1",
-		)
-		Expect(err).ToNot(HaveOccurred())
-
-		// then
-		Eventually(func(g Gomega) {
-			g.Expect(WaitNumPods(namespace, 1, name)(env.Cluster)).To(Succeed())
-		}, "60s", "1s").Should(Succeed())
-
-		Expect(failedErr).ToNot(HaveOccurred())
-	})
+			}()
+
+			// when
+			Expect(given.scaleFn(2)).To(Succeed())
+
+			// then
+			Eventually(func(g Gomega) {
+				g.Expect(WaitNumPods(namespace, 2, given.deploymentName)(env.Cluster)).To(Succeed())
+				g.Expect(WaitPodsAvailable(namespace, given.deploymentName)(env.Cluster)).To(Succeed())
+			}, "30s", "1s").Should(Succeed())
+			Expect(failedErr).ToNot(HaveOccurred())
+
+			// when
+			Expect(given.scaleFn(1)).To(Succeed())
+
+			// then
+			Eventually(func(g Gomega) {
+				g.Expect(WaitNumPods(namespace, 1, given.deploymentName)(env.Cluster)).To(Succeed())
+			}, "60s", "1s").Should(Succeed())
+
+			Expect(failedErr).ToNot(HaveOccurred())
+		},
+		Entry("a service", testCase{
+			deploymentName: name,
+			scaleFn: func(replicas int) error {
+				return k8s.RunKubectlE(
+					env.Cluster.GetTesting(),
+					env.Cluster.GetKubectlOptions(namespace),
+					"scale", "deployment", name, "--replicas", strconv.Itoa(replicas),
+				)
+			},
+		}),
+		Entry("a gateway", testCase{
+			deploymentName: "edge-gateway",
+			scaleFn: func(replicas int) error {
+				return env.Cluster.Install(YamlK8s(gatewayInstnace(replicas)))
+			},
+		}),
+	)
 }
diff --git a/test/e2e_env/kubernetes/healthcheck/virtual_probes.go b/test/e2e_env/kubernetes/healthcheck/virtual_probes.go
index 90ab825cb..f2303ef84 100644
--- a/test/e2e_env/kubernetes/healthcheck/virtual_probes.go
+++ b/test/e2e_env/kubernetes/healthcheck/virtual_probes.go
@@ -1,4 +1,4 @@
-package virtual_probes
+package healthcheck
 
 import (
 	. "github.com/onsi/ginkgo/v2"
diff --git a/test/e2e_env/kubernetes/kubernetes_suite_test.go b/test/e2e_env/kubernetes/kubernetes_suite_test.go
index 72fadc9da..ff58dd3eb 100644
--- a/test/e2e_env/kubernetes/kubernetes_suite_test.go
+++ b/test/e2e_env/kubernetes/kubernetes_suite_test.go
@@ -11,8 +11,9 @@ import (
 	"github.com/kumahq/kuma/pkg/test"
 	"github.com/kumahq/kuma/test/e2e_env/kubernetes/env"
 	"github.com/kumahq/kuma/test/e2e_env/kubernetes/graceful"
-	healthcheck "github.com/kumahq/kuma/test/e2e_env/kubernetes/healthcheck"
+	"github.com/kumahq/kuma/test/e2e_env/kubernetes/healthcheck"
 	"github.com/kumahq/kuma/test/e2e_env/kubernetes/jobs"
+	"github.com/kumahq/kuma/test/e2e_env/kubernetes/membership"
 	. "github.com/kumahq/kuma/test/framework"
 )
 
@@ -60,6 +61,11 @@ var _ = SynchronizedBeforeSuite(
 	},
 )
 
+// SynchronizedAfterSuite keeps the main process alive until all other processes finish.
+// Otherwise, we would close port-forward to the CP and remaining tests executed in different processes may fail.
+var _ = SynchronizedAfterSuite(func() {}, func() {})
+
 var _ = Describe("Virtual Probes", healthcheck.VirtualProbes, Ordered)
 var _ = Describe("Graceful", graceful.Graceful, Ordered)
 var _ = Describe("Jobs", jobs.Jobs)
+var _ = Describe("Membership", membership.Membership, Ordered)
diff --git a/test/e2e_env/kubernetes/membership/membership.go b/test/e2e_env/kubernetes/membership/membership.go
new file mode 100644
index 000000000..a067d7bcd
--- /dev/null
+++ b/test/e2e_env/kubernetes/membership/membership.go
@@ -0,0 +1,91 @@
+package membership
+
+import (
+	"fmt"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/kumahq/kuma/test/e2e_env/kubernetes/env"
+	. "github.com/kumahq/kuma/test/framework"
+	"github.com/kumahq/kuma/test/framework/deployments/testserver"
+)
+
+func Membership() {
+	const ns1 = "membership-1"
+	const ns2 = "membership-2"
+	const mesh1 = "membership-1"
+	const mesh2 = "membership-2"
+
+	meshAllowingNamespace := func(mesh, ns string) string {
+		return fmt.Sprintf(`apiVersion: kuma.io/v1alpha1
+kind: Mesh
+metadata:
+  name: %s
+spec:
+  constraints:
+    dataplaneProxy:
+      requirements:
+      - tags:
+          k8s.kuma.io/namespace: %s`, mesh, ns)
+	}
+	BeforeAll(func() {
+		err := NewClusterSetup().
+			Install(NamespaceWithSidecarInjection(ns1)).
+			Install(YamlK8s(meshAllowingNamespace(mesh1, ns1))).
+			Install(NamespaceWithSidecarInjection(ns2)).
+			Install(YamlK8s(meshAllowingNamespace(mesh2, ns2))).
+			Setup(env.Cluster)
+		Expect(err).ToNot(HaveOccurred())
+	})
+	E2EAfterAll(func() {
+		Expect(env.Cluster.TriggerDeleteNamespace(ns1)).To(Succeed())
+		Expect(env.Cluster.DeleteMesh(mesh1)).To(Succeed())
+		Expect(env.Cluster.TriggerDeleteNamespace(ns2)).To(Succeed())
+		Expect(env.Cluster.DeleteMesh(mesh2)).To(Succeed())
+	})
+
+	It("should take into account membership when dp is connecting to the CP", func() {
+		// when
+		err := testserver.Install(
+			testserver.WithNamespace(ns1),
+			testserver.WithMesh(mesh1),
+		)(env.Cluster)
+		Expect(err).ToNot(HaveOccurred())
+
+		// then
+		Eventually(func() (string, error) {
+			return env.Cluster.GetKumactlOptions().RunKumactlAndGetOutput("get", "dataplanes", "--mesh", mesh1)
+		}, "30s", "1s").Should(ContainSubstring("test-server"))
+
+		// when trying to change mesh to demo
+		err = testserver.Install(
+			testserver.WithNamespace(ns1),
+			testserver.WithMesh(mesh2),
+			testserver.WithoutWaitingToBeReady(),
+		)(env.Cluster)
+		Expect(err).ToNot(HaveOccurred())
+
+		// then the client is not allowed to do it
+		// then it's not allowed
+		// todo(jakubdyszkiewicz) uncomment once we can handle CP logs across all parallel executions
+		// Eventually(func() (string, error) {
+		//	return env.Cluster.GetKumaCPLogs()
+		// }, "30s", "1s").Should(ContainSubstring("dataplane cannot be a member of mesh"))
+		Consistently(func() (string, error) {
+			return env.Cluster.GetKumactlOptions().RunKumactlAndGetOutput("get", "dataplanes", "--mesh", mesh2)
+		}, "10s", "5s").ShouldNot(ContainSubstring("test-server"))
+
+		// when a new client is deployed in demo namespace in demo mesh
+		err = testserver.Install(
+			testserver.WithNamespace(ns2),
+			testserver.WithMesh(mesh2),
+		)(env.Cluster)
+		Expect(err).ToNot(HaveOccurred())
+
+		// then it's allowed
+		Eventually(func() (string, error) {
+			return env.Cluster.GetKumactlOptions().RunKumactlAndGetOutput("get", "dataplanes", "--mesh", mesh2)
+		}, "30s", "1s").Should(ContainSubstring("test-server"))
+	})
+}
diff --git a/test/e2e_env/multizone/healthcheck/healthcheck.go b/test/e2e_env/multizone/healthcheck/healthcheck.go
new file mode 100644
index 000000000..be74c225a
--- /dev/null
+++ b/test/e2e_env/multizone/healthcheck/healthcheck.go
@@ -0,0 +1,105 @@
+package healthcheck
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/gruntwork-io/terratest/modules/retry"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	"github.com/pkg/errors"
+
+	"github.com/kumahq/kuma/test/e2e_env/multizone/env"
+	. "github.com/kumahq/kuma/test/framework"
+)
+
+func ApplicationOnUniversalClientOnK8s() {
+	namespace := "healthcheck-app-on-universal"
+	meshName := "healthcheck-app-on-universal"
+
+	BeforeAll(func() {
+		err := env.Global.Install(MTLSMeshUniversal(meshName))
+		Expect(err).ToNot(HaveOccurred())
+
+		err = NewClusterSetup().
+			Install(NamespaceWithSidecarInjection(namespace)).
+			Install(DemoClientK8s(meshName, namespace)).
+			Setup(env.KubeZone1)
+		Expect(err).ToNot(HaveOccurred())
+
+		err = NewClusterSetup().
+			Install(TestServerUniversal("test-server-1", meshName,
+				WithArgs([]string{"echo", "--instance", "dp-universal-1"}),
+				WithProtocol("tcp"))).
+			// This instance doesn't actually start the app
+			Install(TestServerUniversal("test-server-2", meshName,
+				WithArgs([]string{"echo", "--instance", "dp-universal-2"}),
+				WithProtocol("tcp"),
+				ProxyOnly(),
+				ServiceProbe())).
+			Install(TestServerUniversal("test-server-3", meshName,
+				WithArgs([]string{"echo", "--instance", "dp-universal-3"}),
+				WithProtocol("tcp"))).
+			Setup(env.UniZone1)
+		Expect(err).ToNot(HaveOccurred())
+	})
+	E2EAfterAll(func() {
+		Expect(env.Global.DeleteMesh(meshName)).To(Succeed())
+		Expect(env.KubeZone1.DeleteNamespace(namespace)).To(Succeed())
+		Expect(env.UniZone1.DeleteMeshApps(meshName)).To(Succeed())
+	})
+
+	It("should not load balance requests to unhealthy instance", func() {
+		pod, err := PodNameOfApp(env.KubeZone1, "demo-client", namespace)
+		Expect(err).ToNot(HaveOccurred())
+
+		cmd := []string{"curl", "-v", "-m", "3", "--fail", "test-server.mesh"}
+
+		instances := []string{"dp-universal-1", "dp-universal-3"}
+		instanceSet := map[string]bool{}
+
+		_, err = retry.DoWithRetryE(env.KubeZone1.GetTesting(), fmt.Sprintf("kubectl exec %s -- %s", pod, strings.Join(cmd, " ")),
+			100, 500*time.Millisecond, func() (string, error) {
+				stdout, _, err := env.KubeZone1.Exec(namespace, pod, "demo-client", cmd...)
+				if err != nil {
+					return "", err
+				}
+				for _, instance := range instances {
+					if strings.Contains(stdout, instance) {
+						instanceSet[instance] = true
+					}
+				}
+				if len(instanceSet) != len(instances) {
+					return "", errors.Errorf("checked %d/%d instances", len(instanceSet), len(instances))
+				}
+				return "", nil
+			},
+		)
+		Expect(err).ToNot(HaveOccurred())
+
+		var counter1, counter2, counter3 int
+		const numOfRequest = 100
+
+		for i := 0; i < numOfRequest; i++ {
+			var stdout string
+
+			stdout, _, err = env.KubeZone1.Exec(namespace, pod, "demo-client", cmd...)
+			Expect(err).ToNot(HaveOccurred())
+
+			switch {
+			case strings.Contains(stdout, "dp-universal-1"):
+				counter1++
+			case strings.Contains(stdout, "dp-universal-2"):
+				counter2++
+			case strings.Contains(stdout, "dp-universal-3"):
+				counter3++
+			}
+		}
+
+		Expect(counter2).To(Equal(0))
+		Expect(counter1 > 0).To(BeTrue())
+		Expect(counter3 > 0).To(BeTrue())
+		Expect(counter1 + counter3).To(Equal(numOfRequest))
+	})
+}
diff --git a/test/e2e_env/multizone/inspect/inspect.go b/test/e2e_env/multizone/inspect/inspect.go
new file mode 100644
index 000000000..91ae5e7a6
--- /dev/null
+++ b/test/e2e_env/multizone/inspect/inspect.go
@@ -0,0 +1,82 @@
+package inspect
+
+import (
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/kumahq/kuma/test/e2e_env/multizone/env"
+	. "github.com/kumahq/kuma/test/framework"
+)
+
+func Inspect() {
+	const meshName = "inspect"
+
+	BeforeAll(func() {
+		Expect(env.Global.Install(MTLSMeshUniversal(meshName))).To(Succeed())
+		Expect(WaitForMesh(meshName, env.Zones())).To(Succeed())
+
+		err := env.UniZone1.Install(TestServerUniversal("test-server", meshName,
+			WithArgs([]string{"echo", "--instance", "echo"}),
+		))
+		Expect(err).ToNot(HaveOccurred())
+	})
+
+	E2EAfterAll(func() {
+		Expect(env.UniZone1.DeleteMeshApps(meshName)).To(Succeed())
+		Expect(env.Global.DeleteMesh(meshName)).To(Succeed())
+	})
+
+	Context("Dataplane", func() {
+		It("should execute config dump from Global CP", func() {
+			Eventually(func(g Gomega) {
+				out, err := env.Global.GetKumactlOptions().RunKumactlAndGetOutput("inspect", "dataplane", "kuma-4.test-server", "--config-dump", "--mesh", meshName)
+				g.Expect(err).ToNot(HaveOccurred())
+				g.Expect(out).Should(ContainSubstring(`"dataplane.proxyType": "dataplane"`))
+			}, "30s", "1s").Should(Succeed())
+		})
+
+		It("should execute config dump from Zone CP", func() {
+			Eventually(func(g Gomega) {
+				out, err := env.UniZone1.GetKumactlOptions().RunKumactlAndGetOutput("inspect", "dataplane", "test-server", "--config-dump", "--mesh", meshName)
+				g.Expect(err).ToNot(HaveOccurred())
+				g.Expect(out).Should(ContainSubstring(`"dataplane.proxyType": "dataplane"`))
+			}, "30s", "1s").Should(Succeed())
+		})
+	})
+
+	Context("ZoneIngress", func() {
+		It("should execute config dump from Global CP", func() {
+			Eventually(func(g Gomega) {
+				out, err := env.Global.GetKumactlOptions().RunKumactlAndGetOutput("inspect", "zoneingress", "kuma-4.ingress", "--config-dump")
+				g.Expect(err).ToNot(HaveOccurred())
+				g.Expect(out).Should(ContainSubstring(`"dataplane.proxyType": "ingress"`))
+			}, "30s", "1s").Should(Succeed())
+		})
+
+		It("should execute config dump from Zone CP", func() {
+			Eventually(func(g Gomega) {
+				out, err := env.UniZone1.GetKumactlOptions().RunKumactlAndGetOutput("inspect", "zoneingress", "ingress", "--config-dump")
+				g.Expect(err).ToNot(HaveOccurred())
+				g.Expect(out).Should(ContainSubstring(`"dataplane.proxyType": "ingress"`))
+			}, "30s", "1s").Should(Succeed())
+		})
+	})
+
+	Context("ZoneEgress", func() {
+		It("should execute config dump from Global CP", func() {
+			Eventually(func(g Gomega) {
+				out, err := env.Global.GetKumactlOptions().RunKumactlAndGetOutput("inspect", "zoneegress", "kuma-4.egress", "--config-dump")
+				g.Expect(err).ToNot(HaveOccurred())
+				g.Expect(out).Should(ContainSubstring(`"dataplane.proxyType": "egress"`))
+			}, "30s", "1s").Should(Succeed())
+		})
+
+		It("should execute config dump from Zone CP", func() {
+			Eventually(func(g Gomega) {
+				out, err := env.UniZone1.GetKumactlOptions().RunKumactlAndGetOutput("inspect", "zoneegress", "egress", "--config-dump")
+				g.Expect(err).ToNot(HaveOccurred())
+				g.Expect(out).Should(ContainSubstring(`"dataplane.proxyType": "egress"`))
+			}, "30s", "1s").Should(Succeed())
+		})
+	})
+}
diff --git a/test/e2e_env/multizone/multizone_suite_test.go b/test/e2e_env/multizone/multizone_suite_test.go
index b6fdf85f4..0982b6877 100644
--- a/test/e2e_env/multizone/multizone_suite_test.go
+++ b/test/e2e_env/multizone/multizone_suite_test.go
@@ -11,6 +11,8 @@ import (
 	"github.com/kumahq/kuma/pkg/config/core"
 	"github.com/kumahq/kuma/pkg/test"
 	"github.com/kumahq/kuma/test/e2e_env/multizone/env"
+	"github.com/kumahq/kuma/test/e2e_env/multizone/healthcheck"
+	"github.com/kumahq/kuma/test/e2e_env/multizone/inspect"
 	"github.com/kumahq/kuma/test/e2e_env/multizone/trafficpermission"
 	"github.com/kumahq/kuma/test/e2e_env/multizone/trafficroute"
 	. "github.com/kumahq/kuma/test/framework"
@@ -180,3 +182,5 @@ var _ = SynchronizedBeforeSuite(
 
 var _ = Describe("TrafficPermission", trafficpermission.TrafficPermission, Ordered)
 var _ = Describe("TrafficRoute", trafficroute.TrafficRoute, Ordered)
+var _ = Describe("Healtcheck", healthcheck.ApplicationOnUniversalClientOnK8s, Ordered)
+var _ = Describe("Inspect", inspect.Inspect, Ordered)
diff --git a/test/e2e_env/universal/healthcheck/policy.go b/test/e2e_env/universal/healthcheck/policy.go
new file mode 100644
index 000000000..369fbc39f
--- /dev/null
+++ b/test/e2e_env/universal/healthcheck/policy.go
@@ -0,0 +1,165 @@
+package healthcheck
+
+import (
+	"encoding/base64"
+	"fmt"
+	"strings"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/kumahq/kuma/test/e2e_env/universal/env"
+	. "github.com/kumahq/kuma/test/framework"
+)
+
+func Policy() {
+	Describe("HTTP", func() {
+		meshName := "healthcheck-http"
+		healthCheck := func(mesh, method, status string) string {
+			return fmt.Sprintf(`
+type: HealthCheck
+name: everything-to-backend
+mesh: %s
+sources:
+- match:
+    kuma.io/service: '*'
+destinations:
+- match:
+    kuma.io/service: test-server
+conf:
+  interval: 10s
+  timeout: 2s
+  unhealthyThreshold: 3
+  healthyThreshold: 1
+  failTrafficOnPanic: true
+  noTrafficInterval: 1s
+  healthyPanicThreshold: 0
+  reuse_connection: true
+  http: 
+    path: /%s
+    expectedStatuses: 
+    - %s`, mesh, method, status)
+		}
+		BeforeAll(func() {
+			err := NewClusterSetup().
+				Install(MeshUniversal(meshName)).
+				Install(YamlUniversal(healthCheck(meshName, "health", "200"))).
+				Install(DemoClientUniversal("dp-demo-client", meshName,
+					WithTransparentProxy(true)),
+				).
+				Install(TestServerUniversal("test-server", meshName, WithArgs([]string{"health-check", "http"}))).
+				Setup(env.Cluster)
+			Expect(err).ToNot(HaveOccurred())
+		})
+
+		E2EAfterAll(func() {
+			Expect(env.Cluster.DeleteMeshApps(meshName)).To(Succeed())
+			Expect(env.Cluster.DeleteMesh(meshName)).To(Succeed())
+		})
+
+		It("should mark host as unhealthy if it doesn't reply on health checks", func() {
+			// check that test-server is healthy
+			cmd := []string{"curl", "--fail", "test-server.mesh/content"}
+			stdout, _, err := env.Cluster.ExecWithRetries("", "", "dp-demo-client", cmd...)
+			Expect(err).ToNot(HaveOccurred())
+			Expect(stdout).To(ContainSubstring("response"))
+
+			// update HealthCheck policy to check for another status code
+			Expect(YamlUniversal(healthCheck(meshName, "are-you-healthy", "500"))(env.Cluster)).To(Succeed())
+
+			// wait cluster 'test-server' to be marked as unhealthy
+			Eventually(func() bool {
+				cmd := []string{"/bin/bash", "-c", "\"curl localhost:9901/clusters | grep test-server\""}
+				stdout, _, err := env.Cluster.ExecWithRetries("", "", "dp-demo-client", cmd...)
+				if err != nil {
+					return false
+				}
+				return strings.Contains(stdout, "health_flags::/failed_active_hc")
+			}, "30s", "500ms").Should(BeTrue())
+
+			// check that test-server is unhealthy
+			cmd = []string{"curl", "test-server.mesh/content"}
+			stdout, _, err = env.Cluster.ExecWithRetries("", "", "dp-demo-client", cmd...)
+			Expect(err).ToNot(HaveOccurred())
+			Expect(stdout).To(ContainSubstring("no healthy upstream"))
+		})
+	}, Ordered)
+
+	Describe("TCP", func() {
+		healthCheck := func(mesh, send, recv string) string {
+			sendBase64 := base64.StdEncoding.EncodeToString([]byte(send))
+			recvBase64 := base64.StdEncoding.EncodeToString([]byte(recv))
+
+			return fmt.Sprintf(`
+type: HealthCheck
+name: gateway-to-backend
+mesh: %s
+sources:
+- match:
+    kuma.io/service: '*'
+destinations:
+- match:
+    kuma.io/service: test-server
+conf:
+  interval: 10s
+  timeout: 2s
+  unhealthyThreshold: 3
+  healthyThreshold: 1
+  failTrafficOnPanic: true
+  noTrafficInterval: 1s
+  healthyPanicThreshold: 0
+  reuse_connection: true
+  tcp: 
+    send: %s
+    receive:
+    - %s`, mesh, sendBase64, recvBase64)
+		}
+		meshName := "healthcheck-tcp"
+		BeforeAll(func() {
+			err := NewClusterSetup().
+				Install(MeshUniversal(meshName)).
+				Install(YamlUniversal(healthCheck(meshName, "foo", "bar"))).
+				Install(DemoClientUniversal("dp-demo-client", meshName,
+					WithTransparentProxy(true)),
+				).
+				Install(TestServerUniversal("test-server", meshName,
+					WithArgs([]string{"health-check", "tcp"}),
+					WithProtocol("tcp")),
+				).
+				Setup(env.Cluster)
+			Expect(err).ToNot(HaveOccurred())
+		})
+		E2EAfterAll(func() {
+			Expect(env.Cluster.DeleteMeshApps(meshName)).To(Succeed())
+			Expect(env.Cluster.DeleteMesh(meshName)).To(Succeed())
+		})
+
+		It("should mark host as unhealthy if it doesn't reply on health checks", func() {
+			// check that test-server is healthy
+			cmd := []string{"/bin/bash", "-c", "\"echo request | nc test-server.mesh 80\""}
+			stdout, _, err := env.Cluster.ExecWithRetries("", "", "dp-demo-client", cmd...)
+			Expect(err).ToNot(HaveOccurred())
+			Expect(stdout).To(ContainSubstring("response"))
+
+			// update HealthCheck policy to check for another 'recv' line
+			Expect(YamlUniversal(healthCheck(meshName, "foo", "baz"))(env.Cluster)).To(Succeed())
+
+			// wait cluster 'test-server' to be marked as unhealthy
+			Eventually(func() bool {
+				cmd := []string{"/bin/bash", "-c", "\"curl localhost:9901/clusters | grep test-server\""}
+				stdout, _, err := env.Cluster.ExecWithRetries("", "", "dp-demo-client", cmd...)
+				if err != nil {
+					return false
+				}
+				return strings.Contains(stdout, "health_flags::/failed_active_hc")
+			}, "30s", "500ms").Should(BeTrue())
+
+			cmd = []string{"/bin/bash", "-c", "\"echo request | nc test-server.mesh 80\""}
+			stdout, _, _ = env.Cluster.ExecWithRetries("", "", "dp-demo-client", cmd...)
+
+			// there is no real attempt to setup a connection with test-server, but Envoy may return either
+			// empty response with EXIT_CODE = 0, or  'Ncat: Connection reset by peer.' with EXIT_CODE = 1
+			Expect(stdout).To(Or(BeEmpty(), ContainSubstring("Ncat: Connection reset by peer.")))
+		})
+	}, Ordered)
+}
diff --git a/test/e2e_env/universal/membership/membership.go b/test/e2e_env/universal/membership/membership.go
new file mode 100644
index 000000000..5d476a459
--- /dev/null
+++ b/test/e2e_env/universal/membership/membership.go
@@ -0,0 +1,58 @@
+package membership
+
+import (
+	"fmt"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/kumahq/kuma/test/e2e_env/universal/env"
+	. "github.com/kumahq/kuma/test/framework"
+)
+
+func Membership() {
+	meshName := "membership"
+	mesh := `
+type: Mesh
+name: %s
+constraints:
+  dataplaneProxy:
+    requirements:
+    - tags:
+        kuma.io/service: demo-client
+    restrictions:
+    - tags:
+        kuma.io/service: test-server
+`
+
+	BeforeAll(func() {
+		Expect(YamlUniversal(fmt.Sprintf(mesh, meshName))(env.Cluster)).To(Succeed())
+	})
+	E2EAfterAll(func() {
+		Expect(env.Cluster.DeleteMeshApps(meshName)).To(Succeed())
+		Expect(env.Cluster.DeleteMesh(meshName)).To(Succeed())
+	})
+	It("should take into account membership when dp is connecting to the CP", func() {
+		// when demo client is trying to connect
+		err := DemoClientUniversal(AppModeDemoClient, meshName)(env.Cluster)
+		Expect(err).ToNot(HaveOccurred())
+
+		// then it's allowed
+		Eventually(func() (string, error) {
+			return env.Cluster.GetKumactlOptions().RunKumactlAndGetOutput("get", "dataplanes")
+		}, "30s", "1s").ShouldNot(ContainSubstring(AppModeDemoClient))
+
+		// when test server is trying to connect
+		err = TestServerUniversal("test-server", meshName, WithArgs([]string{"echo", "--instance", "echo-v1"}))(env.Cluster)
+		Expect(err).ToNot(HaveOccurred())
+
+		// then it's not allowed
+		// todo(jakubdyszkiewicz) uncomment once we can handle CP logs across all parallel executions
+		// Eventually(func() (string, error) {
+		//	return env.Cluster.GetKumaCPLogs()
+		// }, "30s", "1s").Should(ContainSubstring("dataplane cannot be a member of mesh"))
+		Consistently(func() (string, error) {
+			return env.Cluster.GetKumactlOptions().RunKumactlAndGetOutput("get", "dataplanes")
+		}, "10s", "5s").ShouldNot(ContainSubstring("test-server"))
+	})
+}
diff --git a/test/e2e_env/universal/universal_suite_test.go b/test/e2e_env/universal/universal_suite_test.go
index 732d92dcf..6a9a22ee8 100644
--- a/test/e2e_env/universal/universal_suite_test.go
+++ b/test/e2e_env/universal/universal_suite_test.go
@@ -14,6 +14,7 @@ import (
 	"github.com/kumahq/kuma/test/e2e_env/universal/externalservices"
 	"github.com/kumahq/kuma/test/e2e_env/universal/healthcheck"
 	"github.com/kumahq/kuma/test/e2e_env/universal/inspect"
+	"github.com/kumahq/kuma/test/e2e_env/universal/membership"
 	. "github.com/kumahq/kuma/test/framework"
 )
 
@@ -57,6 +58,8 @@ var _ = SynchronizedBeforeSuite(
 var _ = Describe("User Auth", auth.UserAuth)
 var _ = Describe("DP Auth", auth.DpAuth, Ordered)
 var _ = Describe("HealthCheck panic threshold", healthcheck.HealthCheckPanicThreshold, Ordered)
+var _ = Describe("HealthCheck", healthcheck.Policy)
 var _ = Describe("Service Probes", healthcheck.ServiceProbes, Ordered)
 var _ = Describe("External Services", externalservices.ExternalServiceHostHeader, Ordered)
 var _ = Describe("Inspect", inspect.Inspect, Ordered)
+var _ = Describe("Membership", membership.Membership, Ordered)