Repository: kumahq/kuma
Pull request #11254 (2024-08-28 14:47:03)

feat(meshtls): policy implementation
### Checklist prior to review

* added e2e multizone and universal
* added validation
* added plugin implementation, which has the same logic as `inbound_proxy_generator.go` for strict/permissive
* configuration of TLS version and cipher is only available when using Mesh/Mesh 
* because we don't have a `to` policy we take the first `from` policy to configure tls version and cipher 
* when MeshTLS policy is defined and mode is not provided we set is as strict


- [x] [Link to relevant issue][1] as well as docs and UI issues -- fix: https://github.com/kumahq/kuma/issues/11134
- [x] This will not break child repos: it doesn't hardcode values (.e.g "kumahq" as a image registry) and it will work on Windows, system specific functions like `syscall.Mkfifo` have equivalent implementation on the other OS --
- [x] Tests (Unit test, E2E tests, manual test on universal and k8s) --
  - Don't forget `ci/` labels to run additional/fewer tests
- [x] Do you need to update [`UPGRADE.md`](../blob/master/UPGRADE.md)? --
- [x] Does it need to be backported according to the [backporting policy](../blob/master/CONTRIBUTING.md#backporting)? ([this](https://github.com/kumahq/kuma/actions/workflows/auto-backport.yaml) GH action will add "backport" label based on these [file globs](https://github.com/kumahq/kuma/blob/master/.github/workflows/auto-backport.yaml#L6), if you want to prevent it from adding the "backport" label use [no-backport-autolabel](https://github.com/kumahq/kuma/blob/master/.github/workflows/auto-backport.yaml#L8) label) --

<!--
> Changelog: skip
-->
<!--
Uncomment the above section to explicitly set a [`> Changelog:` entry here](https://github.com/kumahq/kuma/blob/master/CONTRIBUTING.md#submitting-a-patch)?
-->

[1]: https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword



Patch:
diff --git a/api/common/v1alpha1/tls/tls.go b/api/common/v1alpha1/tls/tls.go
index c30a06347..e42e75b36 100644
--- a/api/common/v1alpha1/tls/tls.go
+++ b/api/common/v1alpha1/tls/tls.go
@@ -4,6 +4,8 @@ package tls
 import (
 	"slices"
 
+	tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
+
 	"github.com/kumahq/kuma/pkg/core/validators"
 )
 
@@ -62,3 +64,43 @@ func ValidateVersion(version *Version) validators.ValidationError {
 
 	return verr
 }
+
+func ToTlsVersion(version *TlsVersion) tlsv3.TlsParameters_TlsProtocol {
+	switch *version {
+	case TLSVersion13:
+		return tlsv3.TlsParameters_TLSv1_3
+	case TLSVersion12:
+		return tlsv3.TlsParameters_TLSv1_2
+	case TLSVersion11:
+		return tlsv3.TlsParameters_TLSv1_1
+	case TLSVersion10:
+		return tlsv3.TlsParameters_TLSv1_0
+	case TLSVersionAuto:
+		fallthrough
+	default:
+		return tlsv3.TlsParameters_TLS_AUTO
+	}
+}
+
+// +kubebuilder:validation:Enum=ECDHE-ECDSA-AES128-GCM-SHA256;ECDHE-ECDSA-AES256-GCM-SHA384;ECDHE-ECDSA-CHACHA20-POLY1305;ECDHE-RSA-AES128-GCM-SHA256;ECDHE-RSA-AES256-GCM-SHA384;ECDHE-RSA-CHACHA20-POLY1305
+type TlsCipher string
+
+const (
+	EcdheEcdsaAes128GcmSha256  TlsCipher = "ECDHE-ECDSA-AES128-GCM-SHA256"
+	EcdheEcdsaAes256GcmSha384  TlsCipher = "ECDHE-ECDSA-AES256-GCM-SHA384"
+	EcdheEcdsaChacha20Poly1305 TlsCipher = "ECDHE-ECDSA-CHACHA20-POLY1305"
+	EcdheRsaAes128GcmSha256    TlsCipher = "ECDHE-RSA-AES128-GCM-SHA256"
+	EcdheRsaAes256GcmSha384    TlsCipher = "ECDHE-RSA-AES256-GCM-SHA384"
+	EcdheRsaChacha20Poly1305   TlsCipher = "ECDHE-RSA-CHACHA20-POLY1305"
+)
+
+var AllCiphers = []string{
+	string(EcdheEcdsaAes128GcmSha256),
+	string(EcdheEcdsaAes256GcmSha384),
+	string(EcdheEcdsaChacha20Poly1305),
+	string(EcdheRsaAes128GcmSha256),
+	string(EcdheRsaAes256GcmSha384),
+	string(EcdheRsaChacha20Poly1305),
+}
+
+type TlsCiphers []TlsCipher
diff --git a/api/common/v1alpha1/tls/zz_generated.deepcopy.go b/api/common/v1alpha1/tls/zz_generated.deepcopy.go
index 70587138a..7f73f6c5d 100644
--- a/api/common/v1alpha1/tls/zz_generated.deepcopy.go
+++ b/api/common/v1alpha1/tls/zz_generated.deepcopy.go
@@ -6,6 +6,25 @@ package tls
 
 import ()
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in TlsCiphers) DeepCopyInto(out *TlsCiphers) {
+	{
+		in := &in
+		*out = make(TlsCiphers, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TlsCiphers.
+func (in TlsCiphers) DeepCopy() TlsCiphers {
+	if in == nil {
+		return nil
+	}
+	out := new(TlsCiphers)
+	in.DeepCopyInto(out)
+	return *out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Version) DeepCopyInto(out *Version) {
 	*out = *in
diff --git a/api/mesh/v1alpha1/mesh.pb.go b/api/mesh/v1alpha1/mesh.pb.go
index 041502655..768471b39 100644
--- a/api/mesh/v1alpha1/mesh.pb.go
+++ b/api/mesh/v1alpha1/mesh.pb.go
@@ -32,8 +32,8 @@ const (
 	// MeshServices are generated and used for configuration
 	Mesh_MeshServices_Everywhere Mesh_MeshServices_Enabled = 1
 	// MeshServices are generated but only used for configuration where
-	// configured via reachableBackendRefs
-	Mesh_MeshServices_ReachableBackendRefs Mesh_MeshServices_Enabled = 2
+	// configured via reachableBackends
+	Mesh_MeshServices_ReachableBackends Mesh_MeshServices_Enabled = 2
 	// MeshServices are generated, used for configuration and kuma.io/services
 	// are not used
 	Mesh_MeshServices_Exclusive Mesh_MeshServices_Enabled = 3
@@ -44,14 +44,14 @@ var (
 	Mesh_MeshServices_Enabled_name = map[int32]string{
 		0: "Disabled",
 		1: "Everywhere",
-		2: "ReachableBackendRefs",
+		2: "ReachableBackends",
 		3: "Exclusive",
 	}
 	Mesh_MeshServices_Enabled_value = map[string]int32{
-		"Disabled":             0,
-		"Everywhere":           1,
-		"ReachableBackendRefs": 2,
-		"Exclusive":            3,
+		"Disabled":          0,
+		"Everywhere":        1,
+		"ReachableBackends": 2,
+		"Exclusive":         3,
 	}
 )
 
@@ -1496,7 +1496,7 @@ var file_api_mesh_v1alpha1_mesh_proto_rawDesc = []byte{
 	0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61,
 	0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f,
 	0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72,
-	0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xba, 0x0b, 0x0a, 0x04, 0x4d, 0x65,
+	0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb7, 0x0b, 0x0a, 0x04, 0x4d, 0x65,
 	0x73, 0x68, 0x12, 0x31, 0x0a, 0x04, 0x6d, 0x74, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
 	0x32, 0x1d, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x61,
 	0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x2e, 0x4d, 0x74, 0x6c, 0x73, 0x52,
@@ -1571,150 +1571,150 @@ var file_api_mesh_v1alpha1_mesh_proto_rawDesc = []byte{
 	0x61, 0x67, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
 	0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
 	0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0xa9, 0x01, 0x0a,
+	0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0xa6, 0x01, 0x0a,
 	0x0c, 0x4d, 0x65, 0x73, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x47, 0x0a,
 	0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2d,
 	0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70,
 	0x68, 0x61, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x2e, 0x4d, 0x65, 0x73, 0x68, 0x53, 0x65, 0x72,
 	0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x07, 0x65,
-	0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x50, 0x0a, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65,
+	0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x4d, 0x0a, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65,
 	0x64, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x10, 0x00, 0x12,
 	0x0e, 0x0a, 0x0a, 0x45, 0x76, 0x65, 0x72, 0x79, 0x77, 0x68, 0x65, 0x72, 0x65, 0x10, 0x01, 0x12,
-	0x18, 0x0a, 0x14, 0x52, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b,
-	0x65, 0x6e, 0x64, 0x52, 0x65, 0x66, 0x73, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x78, 0x63,
-	0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x10, 0x03, 0x3a, 0x5c, 0xaa, 0x8c, 0x89, 0xa6, 0x01, 0x0e,
-	0x0a, 0x0c, 0x4d, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0xaa, 0x8c,
-	0x89, 0xa6, 0x01, 0x06, 0x12, 0x04, 0x4d, 0x65, 0x73, 0x68, 0xaa, 0x8c, 0x89, 0xa6, 0x01, 0x02,
-	0x18, 0x01, 0xaa, 0x8c, 0x89, 0xa6, 0x01, 0x06, 0x22, 0x04, 0x6d, 0x65, 0x73, 0x68, 0xaa, 0x8c,
-	0x89, 0xa6, 0x01, 0x04, 0x52, 0x02, 0x10, 0x01, 0xaa, 0x8c, 0x89, 0xa6, 0x01, 0x08, 0x3a, 0x06,
-	0x0a, 0x04, 0x6d, 0x65, 0x73, 0x68, 0xaa, 0x8c, 0x89, 0xa6, 0x01, 0x0a, 0x3a, 0x08, 0x12, 0x06,
-	0x6d, 0x65, 0x73, 0x68, 0x65, 0x73, 0x22, 0xb0, 0x05, 0x0a, 0x1b, 0x43, 0x65, 0x72, 0x74, 0x69,
-	0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x42,
-	0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79,
-	0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x4e,
-	0x0a, 0x06, 0x64, 0x70, 0x43, 0x65, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36,
-	0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70,
-	0x68, 0x61, 0x31, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x41,
-	0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e,
-	0x44, 0x70, 0x43, 0x65, 0x72, 0x74, 0x52, 0x06, 0x64, 0x70, 0x43, 0x65, 0x72, 0x74, 0x12, 0x2b,
-	0x0a, 0x04, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67,
-	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53,
-	0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x04, 0x63, 0x6f, 0x6e, 0x66, 0x12, 0x48, 0x0a, 0x04, 0x6d,
-	0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x34, 0x2e, 0x6b, 0x75, 0x6d, 0x61,
-	0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43,
-	0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
-	0x69, 0x74, 0x79, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x52,
-	0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x57, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x68, 0x61,
-	0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e,
-	0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x65,
-	0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
-	0x74, 0x79, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x68,
-	0x61, 0x69, 0x6e, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x1a, 0xd4,
-	0x01, 0x0a, 0x06, 0x44, 0x70, 0x43, 0x65, 0x72, 0x74, 0x12, 0x5b, 0x0a, 0x08, 0x72, 0x6f, 0x74,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x6b, 0x75,
+	0x15, 0x0a, 0x11, 0x52, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x61, 0x63, 0x6b,
+	0x65, 0x6e, 0x64, 0x73, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x73,
+	0x69, 0x76, 0x65, 0x10, 0x03, 0x3a, 0x5c, 0xaa, 0x8c, 0x89, 0xa6, 0x01, 0x0e, 0x0a, 0x0c, 0x4d,
+	0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0xaa, 0x8c, 0x89, 0xa6, 0x01,
+	0x06, 0x12, 0x04, 0x4d, 0x65, 0x73, 0x68, 0xaa, 0x8c, 0x89, 0xa6, 0x01, 0x02, 0x18, 0x01, 0xaa,
+	0x8c, 0x89, 0xa6, 0x01, 0x06, 0x22, 0x04, 0x6d, 0x65, 0x73, 0x68, 0xaa, 0x8c, 0x89, 0xa6, 0x01,
+	0x04, 0x52, 0x02, 0x10, 0x01, 0xaa, 0x8c, 0x89, 0xa6, 0x01, 0x08, 0x3a, 0x06, 0x0a, 0x04, 0x6d,
+	0x65, 0x73, 0x68, 0xaa, 0x8c, 0x89, 0xa6, 0x01, 0x0a, 0x3a, 0x08, 0x12, 0x06, 0x6d, 0x65, 0x73,
+	0x68, 0x65, 0x73, 0x22, 0xb0, 0x05, 0x0a, 0x1b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+	0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x42, 0x61, 0x63, 0x6b,
+	0x65, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x4e, 0x0a, 0x06, 0x64,
+	0x70, 0x43, 0x65, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x6b, 0x75,
 	0x6d, 0x61, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
 	0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68,
 	0x6f, 0x72, 0x69, 0x74, 0x79, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x44, 0x70, 0x43,
-	0x65, 0x72, 0x74, 0x2e, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x72, 0x6f,
-	0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19,
-	0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
-	0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x1a, 0x2a, 0x0a, 0x08, 0x52, 0x6f, 0x74,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x4e, 0x0a, 0x09, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x68, 0x61,
-	0x69, 0x6e, 0x12, 0x41, 0x0a, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x69, 0x6d,
-	0x65, 0x6f, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f,
-	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x69,
-	0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0x22, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0a, 0x0a,
-	0x06, 0x53, 0x54, 0x52, 0x49, 0x43, 0x54, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x45, 0x52,
-	0x4d, 0x49, 0x53, 0x53, 0x49, 0x56, 0x45, 0x10, 0x01, 0x22, 0x9b, 0x01, 0x0a, 0x0a, 0x4e, 0x65,
-	0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x12, 0x43, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62,
-	0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6b, 0x75, 0x6d,
-	0x61, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e,
-	0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f,
-	0x75, 0x6e, 0x64, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x48, 0x0a,
-	0x08, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x3c, 0x0a, 0x0b, 0x70, 0x61, 0x73,
-	0x73, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
+	0x65, 0x72, 0x74, 0x52, 0x06, 0x64, 0x70, 0x43, 0x65, 0x72, 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x63,
+	0x6f, 0x6e, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
+	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75,
+	0x63, 0x74, 0x52, 0x04, 0x63, 0x6f, 0x6e, 0x66, 0x12, 0x48, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65,
+	0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x34, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65,
+	0x73, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x65, 0x72, 0x74,
+	0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79,
+	0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f,
+	0x64, 0x65, 0x12, 0x57, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x18,
+	0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65, 0x73,
+	0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69,
+	0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x42,
+	0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e,
+	0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x1a, 0xd4, 0x01, 0x0a, 0x06,
+	0x44, 0x70, 0x43, 0x65, 0x72, 0x74, 0x12, 0x5b, 0x0a, 0x08, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e,
+	0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x65,
+	0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+	0x74, 0x79, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x44, 0x70, 0x43, 0x65, 0x72, 0x74,
+	0x2e, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x72, 0x6f, 0x74, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x69,
+	0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f,
+	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75,
+	0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54,
+	0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x1a, 0x2a, 0x0a, 0x08, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x1a, 0x4e, 0x0a, 0x09, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12,
+	0x41, 0x0a, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75,
+	0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f,
+	0x75, 0x74, 0x22, 0x22, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54,
+	0x52, 0x49, 0x43, 0x54, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53,
+	0x53, 0x49, 0x56, 0x45, 0x10, 0x01, 0x22, 0x9b, 0x01, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x77, 0x6f,
+	0x72, 0x6b, 0x69, 0x6e, 0x67, 0x12, 0x43, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e,
+	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d,
+	0x65, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4e, 0x65, 0x74,
+	0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64,
+	0x52, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x1a, 0x48, 0x0a, 0x08, 0x4f, 0x75,
+	0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x3c, 0x0a, 0x0b, 0x70, 0x61, 0x73, 0x73, 0x74, 0x68,
+	0x72, 0x6f, 0x75, 0x67, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
+	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f,
+	0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0b, 0x70, 0x61, 0x73, 0x73, 0x74, 0x68, 0x72,
+	0x6f, 0x75, 0x67, 0x68, 0x22, 0x71, 0x0a, 0x07, 0x54, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x12,
+	0x26, 0x0a, 0x0e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e,
+	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74,
+	0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x3e, 0x0a, 0x08, 0x62, 0x61, 0x63, 0x6b, 0x65,
+	0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6b, 0x75, 0x6d, 0x61,
+	0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54,
+	0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x08, 0x62,
+	0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x73, 0x22, 0x9f, 0x01, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x63,
+	0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x38,
+	0x0a, 0x08, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
+	0x75, 0x66, 0x2e, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08,
+	0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2b, 0x0a, 0x04,
+	0x63, 0x6f, 0x6e, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f,
+	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72,
+	0x75, 0x63, 0x74, 0x52, 0x04, 0x63, 0x6f, 0x6e, 0x66, 0x22, 0x6f, 0x0a, 0x1b, 0x44, 0x61, 0x74,
+	0x61, 0x64, 0x6f, 0x67, 0x54, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x65,
+	0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72,
+	0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
+	0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d,
+	0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x53,
+	0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x70,
+	0x6c, 0x69, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0xbe, 0x01, 0x0a, 0x1a, 0x5a,
+	0x69, 0x70, 0x6b, 0x69, 0x6e, 0x54, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b,
+	0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x24, 0x0a, 0x0d, 0x74,
+	0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x31, 0x32, 0x38, 0x62, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x08, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x31, 0x32, 0x38, 0x62, 0x69,
+	0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
+	0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
+	0x6e, 0x12, 0x48, 0x0a, 0x11, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x53, 0x70, 0x61, 0x6e, 0x43,
+	0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
+	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42,
+	0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x11, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64,
+	0x53, 0x70, 0x61, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x71, 0x0a, 0x07, 0x4c,
+	0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x12, 0x26, 0x0a, 0x0e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c,
+	0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e,
+	0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x3e,
+	0x0a, 0x08, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x22, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x61,
+	0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63,
+	0x6b, 0x65, 0x6e, 0x64, 0x52, 0x08, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x73, 0x22, 0x7d,
+	0x0a, 0x0e, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64,
+	0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
+	0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x12, 0x0a, 0x04,
+	0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,
+	0x12, 0x2b, 0x0a, 0x04, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17,
 	0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
-	0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0b, 0x70, 0x61, 0x73, 0x73,
-	0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x22, 0x71, 0x0a, 0x07, 0x54, 0x72, 0x61, 0x63, 0x69,
-	0x6e, 0x67, 0x12, 0x26, 0x0a, 0x0e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x42, 0x61, 0x63,
-	0x6b, 0x65, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x66, 0x61,
-	0x75, 0x6c, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x3e, 0x0a, 0x08, 0x62, 0x61,
-	0x63, 0x6b, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6b,
-	0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61,
-	0x31, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64,
-	0x52, 0x08, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x73, 0x22, 0x9f, 0x01, 0x0a, 0x0e, 0x54,
-	0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x12, 0x0a,
-	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
-	0x65, 0x12, 0x38, 0x0a, 0x08, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75,
-	0x65, 0x52, 0x08, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x74,
-	0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12,
-	0x2b, 0x0a, 0x04, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e,
-	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
-	0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x04, 0x63, 0x6f, 0x6e, 0x66, 0x22, 0x6f, 0x0a, 0x1b,
-	0x44, 0x61, 0x74, 0x61, 0x64, 0x6f, 0x67, 0x54, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x42, 0x61,
-	0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x61,
-	0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64,
-	0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x70, 0x6c,
-	0x69, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52,
-	0x0c, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0xbe, 0x01,
-	0x0a, 0x1a, 0x5a, 0x69, 0x70, 0x6b, 0x69, 0x6e, 0x54, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x42,
-	0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03,
-	0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x24,
-	0x0a, 0x0d, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x31, 0x32, 0x38, 0x62, 0x69, 0x74, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x31, 0x32,
-	0x38, 0x62, 0x69, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69,
-	0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72,
-	0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x11, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x53, 0x70,
-	0x61, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
-	0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x11, 0x73, 0x68, 0x61,
-	0x72, 0x65, 0x64, 0x53, 0x70, 0x61, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x71,
-	0x0a, 0x07, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x12, 0x26, 0x0a, 0x0e, 0x64, 0x65, 0x66,
-	0x61, 0x75, 0x6c, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x0e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e,
-	0x64, 0x12, 0x3e, 0x0a, 0x08, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e,
-	0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67,
-	0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x08, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64,
-	0x73, 0x22, 0x7d, 0x0a, 0x0e, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b,
-	0x65, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61,
-	0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12,
-	0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74,
-	0x79, 0x70, 0x65, 0x12, 0x2b, 0x0a, 0x04, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x04, 0x63, 0x6f, 0x6e, 0x66,
-	0x22, 0x2e, 0x0a, 0x18, 0x46, 0x69, 0x6c, 0x65, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x42,
-	0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04,
-	0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68,
-	0x22, 0x33, 0x0a, 0x17, 0x54, 0x63, 0x70, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x42, 0x61,
-	0x63, 0x6b, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x61,
-	0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64,
-	0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0xc1, 0x01, 0x0a, 0x07, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e,
-	0x67, 0x12, 0x3e, 0x0a, 0x1a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x41, 0x77, 0x61,
-	0x72, 0x65, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x41,
-	0x77, 0x61, 0x72, 0x65, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e,
-	0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x7a, 0x6f, 0x6e, 0x65, 0x45, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x7a, 0x6f, 0x6e, 0x65, 0x45, 0x67, 0x72, 0x65, 0x73,
-	0x73, 0x12, 0x56, 0x0a, 0x26, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x46, 0x6f, 0x72, 0x62,
-	0x69, 0x64, 0x4d, 0x65, 0x73, 0x68, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x65,
-	0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x08, 0x52, 0x26, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64,
-	0x4d, 0x65, 0x73, 0x68, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x65, 0x72, 0x76,
-	0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 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,
+	0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x04, 0x63, 0x6f, 0x6e, 0x66, 0x22, 0x2e, 0x0a,
+	0x18, 0x46, 0x69, 0x6c, 0x65, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b,
+	0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74,
+	0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x33, 0x0a,
+	0x17, 0x54, 0x63, 0x70, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x65,
+	0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72,
+	0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
+	0x73, 0x73, 0x22, 0xc1, 0x01, 0x0a, 0x07, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x3e,
+	0x0a, 0x1a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x41, 0x77, 0x61, 0x72, 0x65, 0x4c,
+	0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x08, 0x52, 0x1a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x41, 0x77, 0x61, 0x72,
+	0x65, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x12, 0x1e,
+	0x0a, 0x0a, 0x7a, 0x6f, 0x6e, 0x65, 0x45, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x08, 0x52, 0x0a, 0x7a, 0x6f, 0x6e, 0x65, 0x45, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x56,
+	0x0a, 0x26, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, 0x4d,
+	0x65, 0x73, 0x68, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69,
+	0x63, 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x26,
+	0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, 0x4d, 0x65, 0x73,
+	0x68, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
+	0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 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 (
diff --git a/api/mesh/v1alpha1/mesh.proto b/api/mesh/v1alpha1/mesh.proto
index 355389003..cc0a71295 100644
--- a/api/mesh/v1alpha1/mesh.proto
+++ b/api/mesh/v1alpha1/mesh.proto
@@ -109,8 +109,8 @@ message Mesh {
       // MeshServices are generated and used for configuration
       Everywhere = 1;
       // MeshServices are generated but only used for configuration where
-      // configured via reachableBackendRefs
-      ReachableBackendRefs = 2;
+      // configured via reachableBackends
+      ReachableBackends = 2;
       // MeshServices are generated, used for configuration and kuma.io/services
       // are not used
       Exclusive = 3;
diff --git a/deployments/charts/kuma/crds/kuma.io_meshtlses.yaml b/deployments/charts/kuma/crds/kuma.io_meshtlses.yaml
index e70ea28ef..85dfab353 100644
--- a/deployments/charts/kuma/crds/kuma.io_meshtlses.yaml
+++ b/deployments/charts/kuma/crds/kuma.io_meshtlses.yaml
@@ -58,8 +58,9 @@ spec:
                         'targetRef'
                       properties:
                         mode:
-                          description: Mode defines the behavior of inbound listeners
-                            with regard to traffic encryption.
+                          description: |-
+                            Mode defines the behavior of inbound listeners with regard to traffic encryption.
+                            Default: Strict.
                           enum:
                           - Permissive
                           - Strict
diff --git a/docs/generated/openapi.yaml b/docs/generated/openapi.yaml
index 3eeb69d21..b31025b53 100644
--- a/docs/generated/openapi.yaml
+++ b/docs/generated/openapi.yaml
@@ -10298,6 +10298,8 @@ components:
                         description: >-
                           Mode defines the behavior of inbound listeners with
                           regard to traffic encryption.
+
+                          Default: Strict.
                         enum:
                           - Permissive
                           - Strict
diff --git a/docs/generated/raw/crds/kuma.io_meshtlses.yaml b/docs/generated/raw/crds/kuma.io_meshtlses.yaml
index e70ea28ef..85dfab353 100644
--- a/docs/generated/raw/crds/kuma.io_meshtlses.yaml
+++ b/docs/generated/raw/crds/kuma.io_meshtlses.yaml
@@ -58,8 +58,9 @@ spec:
                         'targetRef'
                       properties:
                         mode:
-                          description: Mode defines the behavior of inbound listeners
-                            with regard to traffic encryption.
+                          description: |-
+                            Mode defines the behavior of inbound listeners with regard to traffic encryption.
+                            Default: Strict.
                           enum:
                           - Permissive
                           - Strict
diff --git a/docs/generated/raw/protos/Mesh.json b/docs/generated/raw/protos/Mesh.json
index a81c57953..345313add 100644
--- a/docs/generated/raw/protos/Mesh.json
+++ b/docs/generated/raw/protos/Mesh.json
@@ -248,7 +248,7 @@
                         0,
                         "Everywhere",
                         1,
-                        "ReachableBackendRefs",
+                        "ReachableBackends",
                         2,
                         "Exclusive",
                         3
diff --git a/docs/madr/decisions/056-meshservice-migration.md b/docs/madr/decisions/056-meshservice-migration.md
index e9a64ed4a..bc889ff17 100644
--- a/docs/madr/decisions/056-meshservice-migration.md
+++ b/docs/madr/decisions/056-meshservice-migration.md
@@ -199,9 +199,9 @@ the listener for the ClusterIP.
 
 3. Users can enable behavior by setting
    - `meshServices: Everywhere` syncs `MeshService` outbounds to all data plane proxies
-   - `meshServices: ReachableBackendRefs` to enable on a case-by-case basis by setting
-     `reachableBackendRefs`:
-     `reachableBackendRefs: { kind: MeshService, labels: {} }`
+   - `meshServices: ReachableBackends` to enable on a case-by-case basis by setting
+     `reachableBackends`:
+     `reachableBackends: { kind: MeshService, labels: {} }`
 
 - **NOTE**: On k8s if the consumer can reach a given local `MeshService`,
   the Kubernetes IP has the behavior of `MeshService`
@@ -350,10 +350,10 @@ or maybe it's OK to switch to local-zone load balancing:
 - `Disabled` is the default
 - `Everywhere` syncs `MeshService` outbounds to all data plane proxies
 - `Exclusive` disables `kuma.io/service` outbounds and enables `MeshService`
-- `ReachableBackendRefs` to generate all `MeshServices` but sync
-  on a case-by-case basis by setting `reachableBackendRefs`:
+- `ReachableBackends` to generate all `MeshServices` but sync
+  on a case-by-case basis by setting `reachableBackends`:
   ```
-  reachableBackendRefs: { kind: MeshService, labels: {} }
+  reachableBackends: { kind: MeshService, labels: {} }
   ```
 
 1. On K8s: if the consumer can reach a given local `MeshService`,
diff --git a/pkg/core/xds/types.go b/pkg/core/xds/types.go
index 2935680a6..f3ad65a5f 100644
--- a/pkg/core/xds/types.go
+++ b/pkg/core/xds/types.go
@@ -5,6 +5,7 @@ import (
 	"slices"
 	"strings"
 
+	tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
 	"github.com/pkg/errors"
 
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
@@ -53,34 +54,25 @@ type TagSelectorSet []mesh_proto.TagSelector
 // DestinationMap holds a set of selectors for all reachable Dataplanes grouped by service name.
 // DestinationMap is based on ServiceName and not on the OutboundInterface because TrafficRoute can introduce new service destinations that were not included in a outbound section.
 // Policies that match on outbound connections also match by service destination name and not outbound interface for the same reason.
-type DestinationMap map[ServiceName]TagSelectorSet
-
-type TlsVersion int32
-
-const (
-	TLSVersionAuto TlsVersion = 0
-	TLSVersion10   TlsVersion = 1
-	TLSVersion11   TlsVersion = 2
-	TLSVersion12   TlsVersion = 3
-	TLSVersion13   TlsVersion = 4
+type (
+	DestinationMap  map[ServiceName]TagSelectorSet
+	ExternalService struct {
+		Protocol                 core_mesh.Protocol
+		TLSEnabled               bool
+		FallbackToSystemCa       bool
+		CaCert                   []byte
+		ClientCert               []byte
+		ClientKey                []byte
+		AllowRenegotiation       bool
+		SkipHostnameVerification bool
+		ServerName               string
+		SANs                     []SAN
+		MinTlsVersion            *tlsv3.TlsParameters_TlsProtocol
+		MaxTlsVersion            *tlsv3.TlsParameters_TlsProtocol
+		OwnerResource            *core_model.TypedResourceIdentifier
+	}
 )
 
-type ExternalService struct {
-	Protocol                 core_mesh.Protocol
-	TLSEnabled               bool
-	FallbackToSystemCa       bool
-	CaCert                   []byte
-	ClientCert               []byte
-	ClientKey                []byte
-	AllowRenegotiation       bool
-	SkipHostnameVerification bool
-	ServerName               string
-	SANs                     []SAN
-	MinTlsVersion            *TlsVersion
-	MaxTlsVersion            *TlsVersion
-	OwnerResource            *core_model.TypedResourceIdentifier
-}
-
 type MatchType string
 
 const (
diff --git a/pkg/plugins/policies/core/ordered/ordered.go b/pkg/plugins/policies/core/ordered/ordered.go
index 2b1eb91eb..b01e693b7 100644
--- a/pkg/plugins/policies/core/ordered/ordered.go
+++ b/pkg/plugins/policies/core/ordered/ordered.go
@@ -24,6 +24,8 @@ var Policies = []plugins.PluginName{
 	// Routes have to come first
 	plugins.PluginName(meshhttproute_api.MeshHTTPRouteResourceTypeDescriptor.KumactlArg),
 	plugins.PluginName(meshtcproute_api.MeshTCPRouteResourceTypeDescriptor.KumactlArg),
+	// MeshTLS needs to come before everything because it rebuilds the inbound listeners
+	plugins.PluginName(meshtls_api.MeshTLSResourceTypeDescriptor.KumactlArg),
 	// For other policies order isn't important at the moment
 	plugins.PluginName(meshloadbalancingstrategy_api.MeshLoadBalancingStrategyResourceTypeDescriptor.KumactlArg),
 	// has to be before MeshAccessLog so the plugin can access log filters that are added to the filter chains
@@ -38,7 +40,6 @@ var Policies = []plugins.PluginName{
 	plugins.PluginName(meshhealthcheck_api.MeshHealthCheckResourceTypeDescriptor.KumactlArg),
 	plugins.PluginName(meshretry_api.MeshRetryResourceTypeDescriptor.KumactlArg),
 	plugins.PluginName(meshmetric_api.MeshMetricResourceTypeDescriptor.KumactlArg),
-	plugins.PluginName(meshtls_api.MeshTLSResourceTypeDescriptor.KumactlArg),
 	// MeshProxyPatch comes after all others
 	plugins.PluginName(meshproxypatch_api.MeshProxyPatchResourceTypeDescriptor.KumactlArg),
 }
diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/meshtls.go b/pkg/plugins/policies/meshtls/api/v1alpha1/meshtls.go
index 697ec4746..5f8529f54 100644
--- a/pkg/plugins/policies/meshtls/api/v1alpha1/meshtls.go
+++ b/pkg/plugins/policies/meshtls/api/v1alpha1/meshtls.go
@@ -27,29 +27,6 @@ type From struct {
 	Default Conf `json:"default,omitempty"`
 }
 
-// +kubebuilder:validation:Enum=ECDHE-ECDSA-AES128-GCM-SHA256;ECDHE-ECDSA-AES256-GCM-SHA384;ECDHE-ECDSA-CHACHA20-POLY1305;ECDHE-RSA-AES128-GCM-SHA256;ECDHE-RSA-AES256-GCM-SHA384;ECDHE-RSA-CHACHA20-POLY1305
-type TlsCipher string
-
-const (
-	EcdheEcdsaAes128GcmSha256  TlsCipher = "ECDHE-ECDSA-AES128-GCM-SHA256"
-	EcdheEcdsaAes256GcmSha384  TlsCipher = "ECDHE-ECDSA-AES256-GCM-SHA384"
-	EcdheEcdsaChacha20Poly1305 TlsCipher = "ECDHE-ECDSA-CHACHA20-POLY1305"
-	EcdheRsaAes128GcmSha256    TlsCipher = "ECDHE-RSA-AES128-GCM-SHA256"
-	EcdheRsaAes256GcmSha384    TlsCipher = "ECDHE-RSA-AES256-GCM-SHA384"
-	EcdheRsaChacha20Poly1305   TlsCipher = "ECDHE-RSA-CHACHA20-POLY1305"
-)
-
-var allCiphers = []string{
-	string(EcdheEcdsaAes128GcmSha256),
-	string(EcdheEcdsaAes256GcmSha384),
-	string(EcdheEcdsaChacha20Poly1305),
-	string(EcdheRsaAes128GcmSha256),
-	string(EcdheRsaAes256GcmSha384),
-	string(EcdheRsaChacha20Poly1305),
-}
-
-type TlsCiphers []TlsCipher
-
 // +kubebuilder:validation:Enum=Permissive;Strict
 type Mode string
 
@@ -65,8 +42,9 @@ type Conf struct {
 	TlsVersion *common_tls.Version `json:"tlsVersion,omitempty"`
 
 	// TlsCiphers section for providing ciphers specification.
-	TlsCiphers TlsCiphers `json:"tlsCiphers,omitempty"`
+	TlsCiphers common_tls.TlsCiphers `json:"tlsCiphers,omitempty"`
 
 	// Mode defines the behavior of inbound listeners with regard to traffic encryption.
+	// Default: Strict.
 	Mode *Mode `json:"mode,omitempty"`
 }
diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/schema.yaml b/pkg/plugins/policies/meshtls/api/v1alpha1/schema.yaml
index f60addfab..dd47d1373 100644
--- a/pkg/plugins/policies/meshtls/api/v1alpha1/schema.yaml
+++ b/pkg/plugins/policies/meshtls/api/v1alpha1/schema.yaml
@@ -31,7 +31,9 @@ properties:
                 'targetRef'
               properties:
                 mode:
-                  description: Mode defines the behavior of inbound listeners with regard to traffic encryption.
+                  description: |-
+                    Mode defines the behavior of inbound listeners with regard to traffic encryption.
+                    Default: Strict.
                   enum:
                     - Permissive
                     - Strict
diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/validator.go b/pkg/plugins/policies/meshtls/api/v1alpha1/validator.go
index 85f1aa419..c6b0fdcb0 100644
--- a/pkg/plugins/policies/meshtls/api/v1alpha1/validator.go
+++ b/pkg/plugins/policies/meshtls/api/v1alpha1/validator.go
@@ -13,7 +13,7 @@ func (r *MeshTLSResource) validate() error {
 	var verr validators.ValidationError
 	path := validators.RootedAt("spec")
 	verr.AddErrorAt(path.Field("targetRef"), validateTop(r.Spec.TargetRef))
-	verr.AddErrorAt(path.Field("from"), validateFrom(r.Spec.From))
+	verr.AddErrorAt(path.Field("from"), validateFrom(r.Spec.From, r.Spec.TargetRef.Kind))
 	return verr.OrNil()
 }
 
@@ -22,27 +22,30 @@ func validateTop(targetRef common_api.TargetRef) validators.ValidationError {
 		SupportedKinds: []common_api.TargetRefKind{
 			common_api.Mesh,
 			common_api.MeshSubset,
-			common_api.MeshService,
-			common_api.MeshServiceSubset,
 		},
 	})
 	return targetRefErr
 }
 
-func validateFrom(from []From) validators.ValidationError {
+func validateFrom(from []From, topLevelTargetRef common_api.TargetRefKind) validators.ValidationError {
 	var verr validators.ValidationError
 	fromPath := validators.Root()
 
 	for idx, fromItem := range from {
 		path := fromPath.Index(idx)
 
-		defaultField := path.Field("default")
-		verr.Add(validateDefault(defaultField, fromItem.Default))
+		targetRefErr := mesh.ValidateTargetRef(fromItem.TargetRef, &mesh.ValidateTargetRefOpts{
+			SupportedKinds: []common_api.TargetRefKind{
+				common_api.Mesh,
+			},
+		})
+		verr.AddErrorAt(path.Field("targetRef"), targetRefErr)
+		verr.Add(validateDefault(path.Field("default"), fromItem.Default, topLevelTargetRef))
 	}
 	return verr
 }
 
-func validateDefault(path validators.PathBuilder, conf Conf) validators.ValidationError {
+func validateDefault(path validators.PathBuilder, conf Conf, topLevelTargetRef common_api.TargetRefKind) validators.ValidationError {
 	var verr validators.ValidationError
 
 	if conf.Mode != nil {
@@ -51,18 +54,24 @@ func validateDefault(path validators.PathBuilder, conf Conf) validators.Validati
 		}
 	}
 
-	if !containsAll(allCiphers, conf.TlsCiphers) {
-		verr.AddErrorAt(path.Field("tlsCiphers"), validators.MakeFieldMustBeOneOfErr("tlsCiphers", allCiphers...))
+	if len(conf.TlsCiphers) > 0 && topLevelTargetRef != common_api.Mesh {
+		verr.AddViolationAt(path.Field("tlsCiphers"), "tlsCiphers can only be defined with top level targetRef kind: Mesh")
+	} else if !containsAll(common_tls.AllCiphers, conf.TlsCiphers) {
+		verr.AddErrorAt(path.Field("tlsCiphers"), validators.MakeFieldMustBeOneOfErr("tlsCiphers", common_tls.AllCiphers...))
 	}
 
 	if conf.TlsVersion != nil {
-		verr.AddErrorAt(path.Field("version"), common_tls.ValidateVersion(conf.TlsVersion))
+		if topLevelTargetRef != common_api.Mesh {
+			verr.AddViolationAt(path.Field("tlsVersion"), "tlsVersion can only be defined with top level targetRef kind: Mesh")
+		} else {
+			verr.AddErrorAt(path.Field("tlsVersion"), common_tls.ValidateVersion(conf.TlsVersion))
+		}
 	}
 
 	return verr
 }
 
-func containsAll(main []string, sub TlsCiphers) bool {
+func containsAll(main []string, sub common_tls.TlsCiphers) bool {
 	elementMap := make(map[string]bool)
 
 	for _, element := range main {
diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/zz_generated.deepcopy.go b/pkg/plugins/policies/meshtls/api/v1alpha1/zz_generated.deepcopy.go
index be03ec4af..1ca23ceb4 100644
--- a/pkg/plugins/policies/meshtls/api/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/plugins/policies/meshtls/api/v1alpha1/zz_generated.deepcopy.go
@@ -18,7 +18,7 @@ func (in *Conf) DeepCopyInto(out *Conf) {
 	}
 	if in.TlsCiphers != nil {
 		in, out := &in.TlsCiphers, &out.TlsCiphers
-		*out = make(TlsCiphers, len(*in))
+		*out = make(tls.TlsCiphers, len(*in))
 		copy(*out, *in)
 	}
 	if in.Mode != nil {
@@ -77,22 +77,3 @@ func (in *MeshTLS) DeepCopy() *MeshTLS {
 	in.DeepCopyInto(out)
 	return out
 }
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in TlsCiphers) DeepCopyInto(out *TlsCiphers) {
-	{
-		in := &in
-		*out = make(TlsCiphers, len(*in))
-		copy(*out, *in)
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TlsCiphers.
-func (in TlsCiphers) DeepCopy() TlsCiphers {
-	if in == nil {
-		return nil
-	}
-	out := new(TlsCiphers)
-	in.DeepCopyInto(out)
-	return *out
-}
diff --git a/pkg/plugins/policies/meshtls/k8s/crd/kuma.io_meshtlses.yaml b/pkg/plugins/policies/meshtls/k8s/crd/kuma.io_meshtlses.yaml
index e70ea28ef..85dfab353 100644
--- a/pkg/plugins/policies/meshtls/k8s/crd/kuma.io_meshtlses.yaml
+++ b/pkg/plugins/policies/meshtls/k8s/crd/kuma.io_meshtlses.yaml
@@ -58,8 +58,9 @@ spec:
                         'targetRef'
                       properties:
                         mode:
-                          description: Mode defines the behavior of inbound listeners
-                            with regard to traffic encryption.
+                          description: |-
+                            Mode defines the behavior of inbound listeners with regard to traffic encryption.
+                            Default: Strict.
                           enum:
                           - Permissive
                           - Strict
diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin.go b/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin.go
index 816027dad..0abdba342 100644
--- a/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin.go
+++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin.go
@@ -1,13 +1,32 @@
 package v1alpha1
 
 import (
+	envoy_cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
+	envoy_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
+	envoy_listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	envoy_tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
+	"github.com/envoyproxy/go-control-plane/pkg/resource/v3"
+	"github.com/envoyproxy/go-control-plane/pkg/wellknown"
+
+	common_tls "github.com/kumahq/kuma/api/common/v1alpha1/tls"
+	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
 	"github.com/kumahq/kuma/pkg/core"
 	core_plugins "github.com/kumahq/kuma/pkg/core/plugins"
 	core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
 	core_xds "github.com/kumahq/kuma/pkg/core/xds"
+	xds_types "github.com/kumahq/kuma/pkg/core/xds/types"
 	"github.com/kumahq/kuma/pkg/plugins/policies/core/matchers"
+	core_rules "github.com/kumahq/kuma/pkg/plugins/policies/core/rules"
+	policies_xds "github.com/kumahq/kuma/pkg/plugins/policies/core/xds"
 	api "github.com/kumahq/kuma/pkg/plugins/policies/meshtls/api/v1alpha1"
+	"github.com/kumahq/kuma/pkg/util/pointer"
+	"github.com/kumahq/kuma/pkg/util/proto"
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
+	envoy_listeners "github.com/kumahq/kuma/pkg/xds/envoy/listeners"
+	envoy_names "github.com/kumahq/kuma/pkg/xds/envoy/names"
+	xds_tls "github.com/kumahq/kuma/pkg/xds/envoy/tls"
+	"github.com/kumahq/kuma/pkg/xds/generator"
 )
 
 var (
@@ -26,6 +45,207 @@ func (p plugin) MatchedPolicies(dataplane *core_mesh.DataplaneResource, resource
 }
 
 func (p plugin) Apply(rs *core_xds.ResourceSet, ctx xds_context.Context, proxy *core_xds.Proxy) error {
-	log.V(1).Info("apply not implemented")
+	if proxy.Dataplane == nil {
+		return nil
+	}
+	log.V(1).Info("applying", "proxy-name", proxy.Dataplane.GetMeta().GetName())
+	policies, ok := proxy.Policies.Dynamic[api.MeshTLSType]
+	if !ok {
+		return nil
+	}
+	listeners := policies_xds.GatherListeners(rs)
+	clusters := policies_xds.GatherClusters(rs)
+
+	if err := applyToInbounds(rs, policies.FromRules, listeners.Inbound, proxy, ctx); err != nil {
+		return err
+	}
+	if err := applyToOutbounds(policies.FromRules, clusters.Outbound, clusters.OutboundSplit, proxy.Outbounds, ctx); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func applyToInbounds(
+	rs *core_xds.ResourceSet,
+	fromRules core_rules.FromRules,
+	inboundListeners map[core_rules.InboundListener]*envoy_listener.Listener,
+	proxy *core_xds.Proxy,
+	ctx xds_context.Context,
+) error {
+	for _, inbound := range proxy.Dataplane.Spec.GetNetworking().GetInbound() {
+		iface := proxy.Dataplane.Spec.Networking.ToInboundInterface(inbound)
+
+		listenerKey := core_rules.InboundListener{
+			Address: iface.DataplaneIP,
+			Port:    iface.DataplanePort,
+		}
+		listener, ok := inboundListeners[listenerKey]
+		if !ok {
+			continue
+		}
+		conf := core_rules.ComputeConf[api.Conf](fromRules.Rules[listenerKey], core_rules.MeshSubset())
+		if conf == nil {
+			continue
+		}
+		l, err := configure(proxy, listener, iface, inbound, conf, ctx)
+		if err != nil {
+			return err
+		}
+		if l != nil {
+			rs.Remove(resource.ListenerType, listener.GetName())
+			rs.Add(&core_xds.Resource{
+				Name:     listener.GetName(),
+				Origin:   generator.OriginInbound,
+				Resource: l,
+			})
+		}
+	}
+	return nil
+}
+
+func applyToOutbounds(
+	fromRules core_rules.FromRules,
+	outboundClusters map[string]*envoy_cluster.Cluster,
+	outboundSplitClusters map[string][]*envoy_cluster.Cluster,
+	outbounds xds_types.Outbounds,
+	ctx xds_context.Context,
+) error {
+	targetedClusters := policies_xds.GatherTargetedClusters(
+		outbounds.Filter(xds_types.NonBackendRefFilter),
+		outboundSplitClusters,
+		outboundClusters,
+	)
+	for cluster, serviceName := range targetedClusters {
+		// we shouldn't modify ExternalService
+		// MeshExternalService has different origin
+		if ctx.Mesh.IsExternalService(serviceName) {
+			continue
+		}
+		// there is only one rule always because we're in `Mesh/Mesh`
+		var conf *api.Conf
+		for _, r := range fromRules.Rules {
+			conf = core_rules.ComputeConf[api.Conf](r, core_rules.MeshSubset())
+			break
+		}
+		if conf == nil {
+			continue
+		}
+		if err := configureParams(conf, cluster); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func configureParams(conf *api.Conf, cluster *envoy_cluster.Cluster) error {
+	if cluster.TransportSocket.GetName() != wellknown.TransportSocketTLS {
+		// we only want to configure TLS Version on listeners protected by Kuma's TLS
+		return nil
+	}
+
+	tlsContext := &envoy_tls.UpstreamTlsContext{CommonTlsContext: &envoy_tls.CommonTlsContext{}}
+
+	version := conf.TlsVersion
+	if version != nil {
+		if version.Min != nil {
+			tlsContext.CommonTlsContext.TlsParams = &envoy_tls.TlsParameters{
+				TlsMinimumProtocolVersion: common_tls.ToTlsVersion(version.Min),
+			}
+		}
+		if version.Max != nil {
+			if tlsContext.CommonTlsContext.TlsParams == nil {
+				tlsContext.CommonTlsContext.TlsParams = &envoy_tls.TlsParameters{
+					TlsMaximumProtocolVersion: common_tls.ToTlsVersion(version.Max),
+				}
+			} else {
+				tlsContext.CommonTlsContext.TlsParams.TlsMaximumProtocolVersion = common_tls.ToTlsVersion(version.Max)
+			}
+		}
+	}
+
+	ciphers := conf.TlsCiphers
+	if ciphers != nil {
+		if tlsContext.CommonTlsContext.TlsParams != nil {
+			var cipherSuites []string
+			for _, c := range ciphers {
+				cipherSuites = append(cipherSuites, string(c))
+			}
+			tlsContext.CommonTlsContext.TlsParams.CipherSuites = cipherSuites
+		}
+	}
+
+	log.V(1).Info("computed outbound tlsContext", "tlsContext", tlsContext)
+
+	dst := envoy_tls.UpstreamTlsContext{}
+	err := proto.UnmarshalAnyTo(cluster.TransportSocket.GetTypedConfig(), &dst)
+	if err != nil {
+		return err
+	}
+
+	// this relies on nothing before it modifying TlsParams
+	dst.CommonTlsContext.TlsParams = tlsContext.CommonTlsContext.TlsParams
+	pbst, err := proto.MarshalAnyDeterministic(&dst)
+	if err != nil {
+		return err
+	}
+	cluster.TransportSocket = &envoy_core.TransportSocket{
+		Name: "envoy.transport_sockets.tls",
+		ConfigType: &envoy_core.TransportSocket_TypedConfig{
+			TypedConfig: pbst,
+		},
+	}
+
 	return nil
 }
+
+func configure(
+	proxy *core_xds.Proxy,
+	listener *envoy_listener.Listener,
+	iface mesh_proto.InboundInterface,
+	inbound *mesh_proto.Dataplane_Networking_Inbound,
+	conf *api.Conf,
+	xdsCtx xds_context.Context,
+) (envoy_common.NamedResource, error) {
+	mesh := xdsCtx.Mesh.Resource
+	// Default Strict
+	mode := pointer.DerefOr(conf.Mode, api.ModeStrict)
+	protocol := core_mesh.ParseProtocol(inbound.GetProtocol())
+	localClusterName := envoy_names.GetLocalClusterName(iface.WorkloadPort)
+	cluster := envoy_common.NewCluster(envoy_common.WithService(localClusterName))
+	service := inbound.GetService()
+	routes := generator.GenerateRoutes(proxy, iface, cluster)
+	listenerBuilder := envoy_listeners.NewInboundListenerBuilder(proxy.APIVersion, iface.DataplaneIP, iface.DataplanePort, core_xds.SocketAddressProtocolTCP).
+		Configure(envoy_listeners.TransparentProxying(proxy.Dataplane.Spec.Networking.GetTransparentProxying())).
+		Configure(envoy_listeners.TagsMetadata(inbound.GetTags()))
+
+	switch mode {
+	case api.ModeStrict:
+		listenerBuilder.
+			Configure(envoy_listeners.FilterChain(generator.FilterChainBuilder(true, protocol, proxy, localClusterName, xdsCtx, iface, service, &routes, conf.TlsVersion, conf.TlsCiphers).Configure(
+				envoy_listeners.NetworkRBAC(listener.GetName(), mesh.MTLSEnabled(), proxy.Policies.TrafficPermissions[iface]),
+			)))
+	case api.ModePermissive:
+		listenerBuilder.
+			Configure(envoy_listeners.TLSInspector()).
+			Configure(envoy_listeners.FilterChain(
+				generator.FilterChainBuilder(false, protocol, proxy, localClusterName, xdsCtx, iface, service, &routes, conf.TlsVersion, conf.TlsCiphers).Configure(
+					envoy_listeners.MatchTransportProtocol("raw_buffer"))),
+			).
+			Configure(envoy_listeners.FilterChain(
+				// we need to differentiate between just TLS and Kuma's TLS, because with permissive mode
+				// the app itself might be protected by TLS.
+				generator.FilterChainBuilder(false, protocol, proxy, localClusterName, xdsCtx, iface, service, &routes, conf.TlsVersion, conf.TlsCiphers).Configure(
+					envoy_listeners.MatchTransportProtocol("tls"))),
+			).
+			Configure(envoy_listeners.FilterChain(
+				generator.FilterChainBuilder(true, protocol, proxy, localClusterName, xdsCtx, iface, service, &routes, conf.TlsVersion, conf.TlsCiphers).Configure(
+					envoy_listeners.MatchTransportProtocol("tls"),
+					envoy_listeners.MatchApplicationProtocols(xds_tls.KumaALPNProtocols...),
+					envoy_listeners.NetworkRBAC(listener.GetName(), xdsCtx.Mesh.Resource.MTLSEnabled(), proxy.Policies.TrafficPermissions[iface]),
+				)),
+			)
+	}
+	return listenerBuilder.Build()
+}
diff --git a/pkg/xds/context/context.go b/pkg/xds/context/context.go
index a324ae546..53a0ecda0 100644
--- a/pkg/xds/context/context.go
+++ b/pkg/xds/context/context.go
@@ -107,7 +107,7 @@ type ReachableBackends map[core_model.TypedResourceIdentifier]bool
 
 func (mc *MeshContext) GetReachableBackends(dataplane *core_mesh.DataplaneResource) *ReachableBackends {
 	if dataplane.Spec.Networking.TransparentProxying.GetReachableBackends() == nil {
-		if mc.Resource.Spec.MeshServicesEnabled() == mesh_proto.Mesh_MeshServices_ReachableBackendRefs {
+		if mc.Resource.Spec.MeshServicesEnabled() == mesh_proto.Mesh_MeshServices_ReachableBackends {
 			return &ReachableBackends{}
 		}
 		return nil
diff --git a/pkg/xds/envoy/listeners/filter_chain_configurers.go b/pkg/xds/envoy/listeners/filter_chain_configurers.go
index 5eb8cc507..221873f80 100644
--- a/pkg/xds/envoy/listeners/filter_chain_configurers.go
+++ b/pkg/xds/envoy/listeners/filter_chain_configurers.go
@@ -6,6 +6,7 @@ import (
 	envoy_extensions_filters_http_compressor_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/compressor/v3"
 	envoy_hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
 
+	common_tls "github.com/kumahq/kuma/api/common/v1alpha1/tls"
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
 	core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
 	core_xds "github.com/kumahq/kuma/pkg/core/xds"
@@ -63,10 +64,12 @@ func NetworkDirectResponse(response string) FilterChainBuilderOpt {
 	})
 }
 
-func ServerSideMTLS(mesh *core_mesh.MeshResource, secrets core_xds.SecretsTracker) FilterChainBuilderOpt {
+func ServerSideMTLS(mesh *core_mesh.MeshResource, secrets core_xds.SecretsTracker, tlsVersion *common_tls.Version, tlsCiphers common_tls.TlsCiphers) FilterChainBuilderOpt {
 	return AddFilterChainConfigurer(&v3.ServerSideMTLSConfigurer{
 		Mesh:           mesh,
 		SecretsTracker: secrets,
+		TlsVersion:     tlsVersion,
+		TlsCiphers:     tlsCiphers,
 	})
 }
 
diff --git a/pkg/xds/envoy/listeners/v3/server_mtls_configurer.go b/pkg/xds/envoy/listeners/v3/server_mtls_configurer.go
index 3b9d8db21..6115606cb 100644
--- a/pkg/xds/envoy/listeners/v3/server_mtls_configurer.go
+++ b/pkg/xds/envoy/listeners/v3/server_mtls_configurer.go
@@ -3,7 +3,9 @@ package v3
 import (
 	envoy_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
 	envoy_listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
+	envoy_tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
 
+	common_tls "github.com/kumahq/kuma/api/common/v1alpha1/tls"
 	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/util/proto"
@@ -13,6 +15,8 @@ import (
 type ServerSideMTLSConfigurer struct {
 	Mesh           *core_mesh.MeshResource
 	SecretsTracker core_xds.SecretsTracker
+	TlsVersion     *common_tls.Version
+	TlsCiphers     common_tls.TlsCiphers
 }
 
 var _ FilterChainConfigurer = &ServerSideMTLSConfigurer{}
@@ -25,6 +29,36 @@ func (c *ServerSideMTLSConfigurer) Configure(filterChain *envoy_listener.FilterC
 	if err != nil {
 		return err
 	}
+
+	version := c.TlsVersion
+	if version != nil {
+		if version.Min != nil {
+			tlsContext.CommonTlsContext.TlsParams = &envoy_tls.TlsParameters{
+				TlsMinimumProtocolVersion: common_tls.ToTlsVersion(version.Min),
+			}
+		}
+		if version.Max != nil {
+			if tlsContext.CommonTlsContext.TlsParams == nil {
+				tlsContext.CommonTlsContext.TlsParams = &envoy_tls.TlsParameters{
+					TlsMaximumProtocolVersion: common_tls.ToTlsVersion(version.Max),
+				}
+			} else {
+				tlsContext.CommonTlsContext.TlsParams.TlsMaximumProtocolVersion = common_tls.ToTlsVersion(version.Max)
+			}
+		}
+	}
+
+	ciphers := c.TlsCiphers
+	if len(ciphers) > 0 {
+		if tlsContext.CommonTlsContext.TlsParams != nil {
+			var cipherSuites []string
+			for _, cipher := range ciphers {
+				cipherSuites = append(cipherSuites, string(cipher))
+			}
+			tlsContext.CommonTlsContext.TlsParams.CipherSuites = cipherSuites
+		}
+	}
+
 	if tlsContext != nil {
 		pbst, err := proto.MarshalAnyDeterministic(tlsContext)
 		if err != nil {
diff --git a/pkg/xds/envoy/tls/v3/tls.go b/pkg/xds/envoy/tls/v3/tls.go
index e5434344c..eb4591240 100644
--- a/pkg/xds/envoy/tls/v3/tls.go
+++ b/pkg/xds/envoy/tls/v3/tls.go
@@ -97,7 +97,7 @@ func NewSecretConfigSource(secretName string) *envoy_tls.SdsSecretConfig {
 	}
 }
 
-func UpstreamTlsContextOutsideMesh(systemCaPath string, ca, cert, key []byte, allowRenegotiation, skipHostnameVerification, fallbackToSystemCa bool, hostname, sni string, sans []core_xds.SAN, minTlsVersion, maxTlsVersion *core_xds.TlsVersion) (*envoy_tls.UpstreamTlsContext, error) {
+func UpstreamTlsContextOutsideMesh(systemCaPath string, ca, cert, key []byte, allowRenegotiation, skipHostnameVerification, fallbackToSystemCa bool, hostname, sni string, sans []core_xds.SAN, minTlsVersion, maxTlsVersion *envoy_tls.TlsParameters_TlsProtocol) (*envoy_tls.UpstreamTlsContext, error) {
 	tlsContext := &envoy_tls.UpstreamTlsContext{
 		AllowRenegotiation: allowRenegotiation,
 		Sni:                sni,
@@ -183,16 +183,16 @@ func UpstreamTlsContextOutsideMesh(systemCaPath string, ca, cert, key []byte, al
 
 		if minTlsVersion != nil {
 			tlsContext.CommonTlsContext.TlsParams = &envoy_tls.TlsParameters{
-				TlsMinimumProtocolVersion: envoy_tls.TlsParameters_TlsProtocol(*minTlsVersion),
+				TlsMinimumProtocolVersion: *minTlsVersion,
 			}
 		}
 		if maxTlsVersion != nil {
 			if tlsContext.CommonTlsContext.TlsParams == nil {
 				tlsContext.CommonTlsContext.TlsParams = &envoy_tls.TlsParameters{
-					TlsMaximumProtocolVersion: envoy_tls.TlsParameters_TlsProtocol(*maxTlsVersion),
+					TlsMaximumProtocolVersion: *maxTlsVersion,
 				}
 			} else {
-				tlsContext.CommonTlsContext.TlsParams.TlsMaximumProtocolVersion = envoy_tls.TlsParameters_TlsProtocol(*maxTlsVersion)
+				tlsContext.CommonTlsContext.TlsParams.TlsMaximumProtocolVersion = *maxTlsVersion
 			}
 		}
 	}
diff --git a/pkg/xds/generator/egress/external_services_generator.go b/pkg/xds/generator/egress/external_services_generator.go
index 9d4eac525..4a9442ebe 100644
--- a/pkg/xds/generator/egress/external_services_generator.go
+++ b/pkg/xds/generator/egress/external_services_generator.go
@@ -221,7 +221,7 @@ func (g *ExternalServicesGenerator) addFilterChains(
 			)
 
 			filterChainBuilder := envoy_listeners.NewFilterChainBuilder(apiVersion, names.GetEgressFilterChainName(esName, meshName)).Configure(
-				envoy_listeners.ServerSideMTLS(meshResources.Mesh, secretsTracker),
+				envoy_listeners.ServerSideMTLS(meshResources.Mesh, secretsTracker, nil, nil),
 				envoy_listeners.MatchTransportProtocol("tls"),
 				envoy_listeners.MatchServerNames(sni),
 				envoy_listeners.NetworkRBAC(
diff --git a/pkg/xds/generator/inbound_proxy_generator.go b/pkg/xds/generator/inbound_proxy_generator.go
index 07e9e78ef..5a7a72143 100644
--- a/pkg/xds/generator/inbound_proxy_generator.go
+++ b/pkg/xds/generator/inbound_proxy_generator.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/pkg/errors"
 
+	"github.com/kumahq/kuma/api/common/v1alpha1/tls"
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
 	core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
 	"github.com/kumahq/kuma/pkg/core/validators"
@@ -69,65 +70,11 @@ func (g InboundProxyGenerator) Generate(ctx context.Context, _ *core_xds.Resourc
 		})
 
 		cluster := envoy_common.NewCluster(envoy_common.WithService(localClusterName))
-		routes := envoy_common.Routes{}
-
-		// Iterate over that RateLimits and generate the relevant Routes.
-		// We do assume that the rateLimits resource is sorted, so the most
-		// specific source matches come first.
-		for _, rl := range proxy.Policies.RateLimitsInbound[endpoint] {
-			if rl.Spec.GetConf().GetHttp() == nil {
-				continue
-			}
-
-			routes = append(routes, envoy_common.NewRoute(
-				envoy_common.WithCluster(cluster),
-				envoy_common.WithMatchHeaderRegex(tags.TagsHeaderName, tags.MatchSourceRegex(rl)),
-				envoy_common.WithRateLimit(rl.Spec),
-			))
-		}
-
-		// Add the default fall-back route
-		routes = append(routes, envoy_common.NewRoute(envoy_common.WithCluster(cluster)))
+		routes := GenerateRoutes(proxy, endpoint, cluster)
 
 		// generate LDS resource
 		service := iface.GetService()
 		inboundListenerName := envoy_names.GetInboundListenerName(endpoint.DataplaneIP, endpoint.DataplanePort)
-		filterChainBuilder := func(serverSideMTLS bool) *envoy_listeners.FilterChainBuilder {
-			filterChainBuilder := envoy_listeners.NewFilterChainBuilder(proxy.APIVersion, envoy_common.AnonymousResource)
-			switch protocol {
-			// configuration for HTTP case
-			case core_mesh.ProtocolHTTP, core_mesh.ProtocolHTTP2:
-				filterChainBuilder.
-					Configure(envoy_listeners.HttpConnectionManager(localClusterName, true)).
-					Configure(envoy_listeners.FaultInjection(proxy.Policies.FaultInjections[endpoint]...)).
-					Configure(envoy_listeners.RateLimit(proxy.Policies.RateLimitsInbound[endpoint])).
-					Configure(envoy_listeners.Tracing(xdsCtx.Mesh.GetTracingBackend(proxy.Policies.TrafficTrace), service, envoy_common.TrafficDirectionInbound, "", false)).
-					Configure(envoy_listeners.HttpInboundRoutes(service, routes))
-			case core_mesh.ProtocolGRPC:
-				filterChainBuilder.
-					Configure(envoy_listeners.HttpConnectionManager(localClusterName, true)).
-					Configure(envoy_listeners.GrpcStats()).
-					Configure(envoy_listeners.FaultInjection(proxy.Policies.FaultInjections[endpoint]...)).
-					Configure(envoy_listeners.RateLimit(proxy.Policies.RateLimitsInbound[endpoint])).
-					Configure(envoy_listeners.Tracing(xdsCtx.Mesh.GetTracingBackend(proxy.Policies.TrafficTrace), service, envoy_common.TrafficDirectionInbound, "", false)).
-					Configure(envoy_listeners.HttpInboundRoutes(service, routes))
-			case core_mesh.ProtocolKafka:
-				filterChainBuilder.
-					Configure(envoy_listeners.Kafka(localClusterName)).
-					Configure(envoy_listeners.TcpProxyDeprecated(localClusterName, envoy_common.NewCluster(envoy_common.WithService(localClusterName))))
-			case core_mesh.ProtocolTCP:
-				fallthrough
-			default:
-				// configuration for non-HTTP cases
-				filterChainBuilder.Configure(envoy_listeners.TcpProxyDeprecated(localClusterName, envoy_common.NewCluster(envoy_common.WithService(localClusterName))))
-			}
-			if serverSideMTLS {
-				filterChainBuilder.
-					Configure(envoy_listeners.ServerSideMTLS(xdsCtx.Mesh.Resource, proxy.SecretsTracker))
-			}
-			return filterChainBuilder.
-				Configure(envoy_listeners.Timeout(defaults_mesh.DefaultInboundTimeout(), protocol))
-		}
 
 		listenerBuilder := envoy_listeners.NewInboundListenerBuilder(proxy.APIVersion, endpoint.DataplaneIP, endpoint.DataplanePort, core_xds.SocketAddressProtocolTCP).
 			Configure(envoy_listeners.TransparentProxying(proxy.Dataplane.Spec.Networking.GetTransparentProxying())).
@@ -136,24 +83,24 @@ func (g InboundProxyGenerator) Generate(ctx context.Context, _ *core_xds.Resourc
 		switch xdsCtx.Mesh.Resource.GetEnabledCertificateAuthorityBackend().GetMode() {
 		case mesh_proto.CertificateAuthorityBackend_STRICT:
 			listenerBuilder.
-				Configure(envoy_listeners.FilterChain(filterChainBuilder(true).Configure(
+				Configure(envoy_listeners.FilterChain(FilterChainBuilder(true, protocol, proxy, localClusterName, xdsCtx, endpoint, service, &routes, nil, nil).Configure(
 					envoy_listeners.NetworkRBAC(inboundListenerName, xdsCtx.Mesh.Resource.MTLSEnabled(), proxy.Policies.TrafficPermissions[endpoint]),
 				)))
 		case mesh_proto.CertificateAuthorityBackend_PERMISSIVE:
 			listenerBuilder.
 				Configure(envoy_listeners.TLSInspector()).
 				Configure(envoy_listeners.FilterChain(
-					filterChainBuilder(false).Configure(
+					FilterChainBuilder(false, protocol, proxy, localClusterName, xdsCtx, endpoint, service, &routes, nil, nil).Configure(
 						envoy_listeners.MatchTransportProtocol("raw_buffer"))),
 				).
 				Configure(envoy_listeners.FilterChain(
 					// we need to differentiate between just TLS and Kuma's TLS, because with permissive mode
 					// the app itself might be protected by TLS.
-					filterChainBuilder(false).Configure(
+					FilterChainBuilder(false, protocol, proxy, localClusterName, xdsCtx, endpoint, service, &routes, nil, nil).Configure(
 						envoy_listeners.MatchTransportProtocol("tls"))),
 				).
 				Configure(envoy_listeners.FilterChain(
-					filterChainBuilder(true).Configure(
+					FilterChainBuilder(true, protocol, proxy, localClusterName, xdsCtx, endpoint, service, &routes, nil, nil).Configure(
 						envoy_listeners.MatchTransportProtocol("tls"),
 						envoy_listeners.MatchApplicationProtocols(xds_tls.KumaALPNProtocols...),
 						envoy_listeners.NetworkRBAC(inboundListenerName, xdsCtx.Mesh.Resource.MTLSEnabled(), proxy.Policies.TrafficPermissions[endpoint]),
@@ -175,3 +122,64 @@ func (g InboundProxyGenerator) Generate(ctx context.Context, _ *core_xds.Resourc
 	}
 	return resources, nil
 }
+
+func FilterChainBuilder(serverSideMTLS bool, protocol core_mesh.Protocol, proxy *core_xds.Proxy, localClusterName string, xdsCtx xds_context.Context, endpoint mesh_proto.InboundInterface, service string, routes *envoy_common.Routes, tlsVersion *tls.Version, ciphers tls.TlsCiphers) *envoy_listeners.FilterChainBuilder {
+	filterChainBuilder := envoy_listeners.NewFilterChainBuilder(proxy.APIVersion, envoy_common.AnonymousResource)
+	switch protocol {
+	// configuration for HTTP case
+	case core_mesh.ProtocolHTTP, core_mesh.ProtocolHTTP2:
+		filterChainBuilder.
+			Configure(envoy_listeners.HttpConnectionManager(localClusterName, true)).
+			Configure(envoy_listeners.FaultInjection(proxy.Policies.FaultInjections[endpoint]...)).
+			Configure(envoy_listeners.RateLimit(proxy.Policies.RateLimitsInbound[endpoint])).
+			Configure(envoy_listeners.Tracing(xdsCtx.Mesh.GetTracingBackend(proxy.Policies.TrafficTrace), service, envoy_common.TrafficDirectionInbound, "", false)).
+			Configure(envoy_listeners.HttpInboundRoutes(service, *routes))
+	case core_mesh.ProtocolGRPC:
+		filterChainBuilder.
+			Configure(envoy_listeners.HttpConnectionManager(localClusterName, true)).
+			Configure(envoy_listeners.GrpcStats()).
+			Configure(envoy_listeners.FaultInjection(proxy.Policies.FaultInjections[endpoint]...)).
+			Configure(envoy_listeners.RateLimit(proxy.Policies.RateLimitsInbound[endpoint])).
+			Configure(envoy_listeners.Tracing(xdsCtx.Mesh.GetTracingBackend(proxy.Policies.TrafficTrace), service, envoy_common.TrafficDirectionInbound, "", false)).
+			Configure(envoy_listeners.HttpInboundRoutes(service, *routes))
+	case core_mesh.ProtocolKafka:
+		filterChainBuilder.
+			Configure(envoy_listeners.Kafka(localClusterName)).
+			Configure(envoy_listeners.TcpProxyDeprecated(localClusterName, envoy_common.NewCluster(envoy_common.WithService(localClusterName))))
+	case core_mesh.ProtocolTCP:
+		fallthrough
+	default:
+		// configuration for non-HTTP cases
+		filterChainBuilder.Configure(envoy_listeners.TcpProxyDeprecated(localClusterName, envoy_common.NewCluster(envoy_common.WithService(localClusterName))))
+	}
+	if serverSideMTLS {
+		filterChainBuilder.
+			Configure(envoy_listeners.ServerSideMTLS(xdsCtx.Mesh.Resource, proxy.SecretsTracker, tlsVersion, ciphers))
+	}
+	return filterChainBuilder.
+		Configure(envoy_listeners.Timeout(defaults_mesh.DefaultInboundTimeout(), protocol))
+}
+
+func GenerateRoutes(proxy *core_xds.Proxy, endpoint mesh_proto.InboundInterface, cluster *envoy_common.ClusterImpl) envoy_common.Routes {
+	routes := envoy_common.Routes{}
+
+	// Iterate over that RateLimits and generate the relevant Routes.
+	// We do assume that the rateLimits resource is sorted, so the most
+	// specific source matches come first.
+	for _, rl := range proxy.Policies.RateLimitsInbound[endpoint] {
+		if rl.Spec.GetConf().GetHttp() == nil {
+			continue
+		}
+
+		routes = append(routes, envoy_common.NewRoute(
+			envoy_common.WithCluster(cluster),
+			envoy_common.WithMatchHeaderRegex(tags.TagsHeaderName, tags.MatchSourceRegex(rl)),
+			envoy_common.WithRateLimit(rl.Spec),
+		))
+	}
+
+	// Add the default fall-back route
+	routes = append(routes, envoy_common.NewRoute(envoy_common.WithCluster(cluster)))
+
+	return routes
+}
diff --git a/pkg/xds/generator/prometheus_endpoint_generator.go b/pkg/xds/generator/prometheus_endpoint_generator.go
index cfdddc365..4288cd3b7 100644
--- a/pkg/xds/generator/prometheus_endpoint_generator.go
+++ b/pkg/xds/generator/prometheus_endpoint_generator.go
@@ -113,7 +113,7 @@ func (g PrometheusEndpointGenerator) Generate(ctx context.Context, _ *core_xds.R
 		case mesh_proto.PrometheusTlsConfig_activeMTLSBackend:
 			listenerBuilder = listenerBuilder.Configure(envoy_listeners.FilterChain(
 				envoy_listeners.NewFilterChainBuilder(proxy.APIVersion, envoy_common.AnonymousResource).Configure(
-					envoy_listeners.ServerSideMTLS(xdsCtx.Mesh.Resource, proxy.SecretsTracker),
+					envoy_listeners.ServerSideMTLS(xdsCtx.Mesh.Resource, proxy.SecretsTracker, nil, nil),
 					envoy_listeners.StaticEndpoints(prometheusListenerName,
 						[]*envoy_common.StaticEndpointPath{
 							{
diff --git a/pkg/xds/topology/outbound.go b/pkg/xds/topology/outbound.go
index 5c85bab19..1896b79a3 100644
--- a/pkg/xds/topology/outbound.go
+++ b/pkg/xds/topology/outbound.go
@@ -699,10 +699,10 @@ func createMeshExternalServiceEndpoint(
 			}
 			if tls.Version != nil {
 				if tls.Version.Min != nil {
-					es.MinTlsVersion = pointer.To(toTlsVersion(tls.Version.Min))
+					es.MinTlsVersion = pointer.To(common_tls.ToTlsVersion(tls.Version.Min))
 				}
 				if tls.Version.Max != nil {
-					es.MaxTlsVersion = pointer.To(toTlsVersion(tls.Version.Max))
+					es.MaxTlsVersion = pointer.To(common_tls.ToTlsVersion(tls.Version.Max))
 				}
 			}
 			// Server name and SNI we need to add
@@ -758,23 +758,6 @@ func createMeshExternalServiceEndpoint(
 	return nil
 }
 
-func toTlsVersion(version *common_tls.TlsVersion) core_xds.TlsVersion {
-	switch *version {
-	case common_tls.TLSVersion13:
-		return core_xds.TLSVersion13
-	case common_tls.TLSVersion12:
-		return core_xds.TLSVersion12
-	case common_tls.TLSVersion11:
-		return core_xds.TLSVersion11
-	case common_tls.TLSVersion10:
-		return core_xds.TLSVersion10
-	case common_tls.TLSVersionAuto:
-		fallthrough
-	default:
-		return core_xds.TLSVersionAuto
-	}
-}
-
 func createExternalServiceEndpoint(
 	ctx context.Context,
 	outbound core_xds.EndpointMap,


Test patch:
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml
index 31568514d..9ce974c98 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml
@@ -6614,8 +6614,9 @@ spec:
                         'targetRef'
                       properties:
                         mode:
-                          description: Mode defines the behavior of inbound listeners
-                            with regard to traffic encryption.
+                          description: |-
+                            Mode defines the behavior of inbound listeners with regard to traffic encryption.
+                            Default: Strict.
                           enum:
                           - Permissive
                           - Strict
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml
index bd82c8c58..257835b7d 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml
@@ -6614,8 +6614,9 @@ spec:
                         'targetRef'
                       properties:
                         mode:
-                          description: Mode defines the behavior of inbound listeners
-                            with regard to traffic encryption.
+                          description: |-
+                            Mode defines the behavior of inbound listeners with regard to traffic encryption.
+                            Default: Strict.
                           enum:
                           - Permissive
                           - Strict
diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml
index c8ce4018c..b94bff7b3 100644
--- a/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml
+++ b/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml
@@ -6634,8 +6634,9 @@ spec:
                         'targetRef'
                       properties:
                         mode:
-                          description: Mode defines the behavior of inbound listeners
-                            with regard to traffic encryption.
+                          description: |-
+                            Mode defines the behavior of inbound listeners with regard to traffic encryption.
+                            Default: Strict.
                           enum:
                           - Permissive
                           - Strict
diff --git a/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml b/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml
index f208f4b79..dece38213 100644
--- a/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml
+++ b/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml
@@ -8104,8 +8104,9 @@ spec:
                         'targetRef'
                       properties:
                         mode:
-                          description: Mode defines the behavior of inbound listeners
-                            with regard to traffic encryption.
+                          description: |-
+                            Mode defines the behavior of inbound listeners with regard to traffic encryption.
+                            Default: Strict.
                           enum:
                           - Permissive
                           - Strict
diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-invalid.input.yaml b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-invalid.input.yaml
index 82bf36e08..c419087a9 100644
--- a/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-invalid.input.yaml
+++ b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-invalid.input.yaml
@@ -1,7 +1,9 @@
 targetRef:
-  kind: MeshService
-  name: svc-1
+  kind: Mesh
 from:
+  - targetRef:
+      kind: MeshHTTPRoute
+    default: {}
   - targetRef:
       kind: Mesh
     default:
diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-invalid.output.yaml b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-invalid.output.yaml
index cb247809e..4200d035b 100644
--- a/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-invalid.output.yaml
+++ b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-invalid.output.yaml
@@ -1,11 +1,13 @@
 violations:
-- field: spec.from[0].default.mode
+- field: spec.from[0].targetRef.kind
+  message: value is not supported
+- field: spec.from[1].default.mode
   message: '"mode" must be one of ["Strict", "Permissive"]'
-- field: spec.from[0].default.tlsCiphers
+- field: spec.from[1].default.tlsCiphers
   message: '"tlsCiphers" must be one of ["ECDHE-ECDSA-AES128-GCM-SHA256", "ECDHE-ECDSA-AES256-GCM-SHA384",
     "ECDHE-ECDSA-CHACHA20-POLY1305", "ECDHE-RSA-AES128-GCM-SHA256", "ECDHE-RSA-AES256-GCM-SHA384",
     "ECDHE-RSA-CHACHA20-POLY1305"]'
-- field: spec.from[0].default.version.min
+- field: spec.from[1].default.tlsVersion.min
   message: '"min" must be one of ["TLSAuto", "TLS10", "TLS11", "TLS12", "TLS13"]'
-- field: spec.from[0].default.version.max
+- field: spec.from[1].default.tlsVersion.max
   message: '"max" must be one of ["TLSAuto", "TLS10", "TLS11", "TLS12", "TLS13"]'
diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-valid.input.yaml b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-valid.input.yaml
index 302b9eb77..d8df82277 100644
--- a/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-valid.input.yaml
+++ b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/full-valid.input.yaml
@@ -1,6 +1,5 @@
 targetRef:
-  kind: MeshService
-  name: svc-1
+  kind: Mesh
 from:
   - targetRef:
       kind: Mesh
diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/invalid-top-level.input.yaml b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/invalid-top-level.input.yaml
new file mode 100644
index 000000000..bfbdf01fe
--- /dev/null
+++ b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/invalid-top-level.input.yaml
@@ -0,0 +1,14 @@
+targetRef:
+  kind: MeshSubset
+  tags:
+    kuma.io/service: svc-1
+from:
+  - targetRef:
+      kind: Mesh
+    default:
+      tlsVersion:
+        min: TLS15
+        max: TLS16
+      tlsCiphers:
+        - "NotExistingCipher"
+      mode: Strict
diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/invalid-top-level.output.yaml b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/invalid-top-level.output.yaml
new file mode 100644
index 000000000..c2a5dfb10
--- /dev/null
+++ b/pkg/plugins/policies/meshtls/api/v1alpha1/testdata/invalid-top-level.output.yaml
@@ -0,0 +1,5 @@
+violations:
+- field: spec.from[0].default.tlsCiphers
+  message: 'tlsCiphers can only be defined with top level targetRef kind: Mesh'
+- field: spec.from[0].default.tlsVersion
+  message: 'tlsVersion can only be defined with top level targetRef kind: Mesh'
diff --git a/pkg/plugins/policies/meshtls/api/v1alpha1/validator_test.go b/pkg/plugins/policies/meshtls/api/v1alpha1/validator_test.go
index 5dd69895e..de3f8f530 100644
--- a/pkg/plugins/policies/meshtls/api/v1alpha1/validator_test.go
+++ b/pkg/plugins/policies/meshtls/api/v1alpha1/validator_test.go
@@ -56,6 +56,10 @@ var _ = Describe("MeshTLS", func() {
 				name: "meshtls-2",
 				file: "full-invalid",
 			}),
+			Entry("invalid top level", testCase{
+				name: "meshtls-3",
+				file: "invalid-top-level",
+			}),
 		)
 	})
 })
diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin_suite_test.go b/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin_suite_test.go
new file mode 100644
index 000000000..2c5db9fae
--- /dev/null
+++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin_suite_test.go
@@ -0,0 +1,11 @@
+package v1alpha1_test
+
+import (
+	"testing"
+
+	"github.com/kumahq/kuma/pkg/test"
+)
+
+func TestPlugin(t *testing.T) {
+	test.RunSpecs(t, "MeshTLS")
+}
diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin_test.go b/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin_test.go
new file mode 100644
index 000000000..e3ed14089
--- /dev/null
+++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/plugin_test.go
@@ -0,0 +1,213 @@
+package v1alpha1_test
+
+import (
+	"fmt"
+	"os"
+	"path"
+
+	envoy_resource "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	core_plugins "github.com/kumahq/kuma/pkg/core/plugins"
+	core_model "github.com/kumahq/kuma/pkg/core/resources/model"
+	core_xds "github.com/kumahq/kuma/pkg/core/xds"
+	xds_types "github.com/kumahq/kuma/pkg/core/xds/types"
+	core_rules "github.com/kumahq/kuma/pkg/plugins/policies/core/rules"
+	api "github.com/kumahq/kuma/pkg/plugins/policies/meshtls/api/v1alpha1"
+	plugin "github.com/kumahq/kuma/pkg/plugins/policies/meshtls/plugin/v1alpha1"
+	"github.com/kumahq/kuma/pkg/test/matchers"
+	"github.com/kumahq/kuma/pkg/test/resources/builders"
+	test_model "github.com/kumahq/kuma/pkg/test/resources/model"
+	"github.com/kumahq/kuma/pkg/test/resources/samples"
+	xds_builders "github.com/kumahq/kuma/pkg/test/xds/builders"
+	util_proto "github.com/kumahq/kuma/pkg/util/proto"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
+	"github.com/kumahq/kuma/pkg/xds/envoy/clusters"
+	"github.com/kumahq/kuma/pkg/xds/envoy/listeners"
+	"github.com/kumahq/kuma/pkg/xds/generator"
+)
+
+func getResource(
+	resourceSet *core_xds.ResourceSet,
+	typ envoy_resource.Type,
+) []byte {
+	resources, err := resourceSet.ListOf(typ).ToDeltaDiscoveryResponse()
+	Expect(err).ToNot(HaveOccurred())
+	actual, err := util_proto.ToYAML(resources)
+	Expect(err).ToNot(HaveOccurred())
+
+	return actual
+}
+
+var _ = Describe("MeshTLS", func() {
+	type testCase struct {
+		caseName    string
+		meshBuilder *builders.MeshBuilder
+	}
+	DescribeTable("should generate proper Envoy config",
+		func(given testCase) {
+			// given
+			mesh := given.meshBuilder
+			context := *xds_builders.Context().
+				WithMeshBuilder(mesh).
+				Build()
+			resourceSet := core_xds.NewResourceSet()
+			secretsTracker := envoy_common.NewSecretsTracker("default", nil)
+			resources := getResources(secretsTracker, mesh)
+			resourceSet.Add(resources...)
+
+			policy := getPolicy(given.caseName)
+
+			proxy := xds_builders.Proxy().
+				WithSecretsTracker(secretsTracker).
+				WithApiVersion(envoy_common.APIV3).
+				WithOutbounds(xds_types.Outbounds{&xds_types.Outbound{
+					LegacyOutbound: builders.Outbound().
+						WithService("outgoing").
+						WithAddress("127.0.0.1").
+						WithPort(27777).Build(),
+				}}).
+				WithDataplane(
+					builders.Dataplane().
+						WithName("test").
+						WithMesh("default").
+						WithAddress("127.0.0.1").
+						WithTransparentProxying(15006, 15001, "ipv4").
+						AddOutbound(
+							builders.Outbound().
+								WithAddress("127.0.0.1").
+								WithPort(27777).
+								WithService("outgoing"),
+						).
+						AddInbound(
+							builders.Inbound().
+								WithAddress("127.0.0.1").
+								WithPort(17777).
+								WithService("backend"),
+						).
+						AddInbound(
+							builders.Inbound().
+								WithAddress("127.0.0.1").
+								WithPort(17778).
+								WithService("frontend"),
+						),
+				).
+				WithPolicies(xds_builders.MatchedPolicies().WithFromPolicy(api.MeshTLSType, getFromRules(policy.Spec.From))).
+				Build()
+
+			plugin := plugin.NewPlugin().(core_plugins.PolicyPlugin)
+
+			// when
+			Expect(plugin.Apply(resourceSet, context, proxy)).To(Succeed())
+
+			// then
+			Expect(getResource(resourceSet, envoy_resource.ListenerType)).
+				To(matchers.MatchGoldenYAML(fmt.Sprintf("testdata/%s.listeners.golden.yaml", given.caseName)))
+			Expect(getResource(resourceSet, envoy_resource.ClusterType)).
+				To(matchers.MatchGoldenYAML(fmt.Sprintf("testdata/%s.clusters.golden.yaml", given.caseName)))
+		},
+		Entry("strict with no mTLS on the mesh", testCase{
+			caseName:    "strict-no-mtls",
+			meshBuilder: samples.MeshDefaultBuilder(),
+		}),
+		Entry("permissive with no mTLS on the mesh", testCase{
+			caseName:    "permissive-no-mtls",
+			meshBuilder: samples.MeshDefaultBuilder(),
+		}),
+		Entry("strict with permissive mTLS on the mesh", testCase{
+			caseName:    "strict-with-permissive-mtls",
+			meshBuilder: samples.MeshMTLSBuilder().WithPermissiveMTLSBackends(),
+		}),
+		Entry("permissive with permissive mTLS on the mesh", testCase{
+			caseName:    "permissive-with-permissive-mtls",
+			meshBuilder: samples.MeshMTLSBuilder().WithPermissiveMTLSBackends(),
+		}),
+	)
+})
+
+func getResources(secretsTracker core_xds.SecretsTracker, mesh *builders.MeshBuilder) []*core_xds.Resource {
+	return []*core_xds.Resource{
+		{
+			Name:   "inbound:127.0.0.1:17777",
+			Origin: generator.OriginInbound,
+			Resource: listeners.NewInboundListenerBuilder(envoy_common.APIV3, "127.0.0.1", 17777, core_xds.SocketAddressProtocolTCP).
+				Configure(listeners.FilterChain(listeners.NewFilterChainBuilder(envoy_common.APIV3, envoy_common.AnonymousResource).
+					Configure(listeners.HttpConnectionManager("127.0.0.1:17777", false)).
+					Configure(
+						listeners.HttpInboundRoutes(
+							"backend",
+							envoy_common.Routes{
+								{
+									Clusters: []envoy_common.Cluster{envoy_common.NewCluster(
+										envoy_common.WithService("backend"),
+										envoy_common.WithWeight(100),
+									)},
+								},
+							},
+						),
+					),
+				)).MustBuild(),
+		},
+		{
+			Name:   "inbound:127.0.0.1:17778",
+			Origin: generator.OriginInbound,
+			Resource: listeners.NewInboundListenerBuilder(envoy_common.APIV3, "127.0.0.1", 17778, core_xds.SocketAddressProtocolTCP).
+				Configure(listeners.FilterChain(listeners.NewFilterChainBuilder(envoy_common.APIV3, envoy_common.AnonymousResource).
+					Configure(listeners.TcpProxyDeprecated("127.0.0.1:17778", envoy_common.NewCluster(envoy_common.WithName("frontend")))),
+				)).MustBuild(),
+		},
+		{
+			Name:   "outgoing",
+			Origin: generator.OriginOutbound,
+			Resource: clusters.NewClusterBuilder(envoy_common.APIV3, "outgoing").
+				Configure(clusters.ClientSideMTLS(secretsTracker, mesh.Build(), "outgoing", true, nil)).
+				MustBuild(),
+		},
+	}
+}
+
+func getPolicy(caseName string) *api.MeshTLSResource {
+	// setup
+	meshTLS := api.NewMeshTLSResource()
+
+	// when
+	contents, err := os.ReadFile(path.Join("testdata", caseName+".policy.yaml"))
+	Expect(err).ToNot(HaveOccurred())
+	err = core_model.FromYAML(contents, &meshTLS.Spec)
+	Expect(err).ToNot(HaveOccurred())
+
+	meshTLS.SetMeta(&test_model.ResourceMeta{
+		Name: "name",
+		Mesh: core_model.DefaultMesh,
+	})
+	// and
+	verr := meshTLS.Validate()
+	Expect(verr).ToNot(HaveOccurred())
+
+	return meshTLS
+}
+
+func getFromRules(froms []api.From) core_rules.FromRules {
+	var rules []*core_rules.Rule
+
+	for _, from := range froms {
+		rules = append(rules, &core_rules.Rule{
+			Subset: core_rules.Subset{},
+			Conf:   from.Default,
+		})
+	}
+
+	return core_rules.FromRules{
+		Rules: map[core_rules.InboundListener]core_rules.Rules{
+			{
+				Address: "127.0.0.1",
+				Port:    17777,
+			}: rules,
+			{
+				Address: "127.0.0.1",
+				Port:    17778,
+			}: rules,
+		},
+	}
+}
diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.clusters.golden.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.clusters.golden.yaml
new file mode 100644
index 000000000..294c7ad43
--- /dev/null
+++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.clusters.golden.yaml
@@ -0,0 +1,5 @@
+resources:
+- name: outgoing
+  resource:
+    '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
+    name: outgoing
diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.listeners.golden.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.listeners.golden.yaml
new file mode 100644
index 000000000..fc92cdf5f
--- /dev/null
+++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.listeners.golden.yaml
@@ -0,0 +1,99 @@
+resources:
+- name: inbound:127.0.0.1:17777
+  resource:
+    '@type': type.googleapis.com/envoy.config.listener.v3.Listener
+    address:
+      socketAddress:
+        address: 127.0.0.1
+        portValue: 17777
+    bindToPort: false
+    enableReusePort: false
+    filterChains:
+    - filterChainMatch:
+        transportProtocol: raw_buffer
+      filters:
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:17777
+          idleTimeout: 7200s
+          statPrefix: localhost_17777
+    - filterChainMatch:
+        transportProtocol: tls
+      filters:
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:17777
+          idleTimeout: 7200s
+          statPrefix: localhost_17777
+    - filterChainMatch:
+        applicationProtocols:
+        - kuma
+        transportProtocol: tls
+      filters:
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:17777
+          idleTimeout: 7200s
+          statPrefix: localhost_17777
+    listenerFilters:
+    - name: envoy.filters.listener.tls_inspector
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
+    metadata:
+      filterMetadata:
+        io.kuma.tags:
+          kuma.io/service: backend
+    name: inbound:127.0.0.1:17777
+    trafficDirection: INBOUND
+- name: inbound:127.0.0.1:17778
+  resource:
+    '@type': type.googleapis.com/envoy.config.listener.v3.Listener
+    address:
+      socketAddress:
+        address: 127.0.0.1
+        portValue: 17778
+    bindToPort: false
+    enableReusePort: false
+    filterChains:
+    - filterChainMatch:
+        transportProtocol: raw_buffer
+      filters:
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:17778
+          idleTimeout: 7200s
+          statPrefix: localhost_17778
+    - filterChainMatch:
+        transportProtocol: tls
+      filters:
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:17778
+          idleTimeout: 7200s
+          statPrefix: localhost_17778
+    - filterChainMatch:
+        applicationProtocols:
+        - kuma
+        transportProtocol: tls
+      filters:
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:17778
+          idleTimeout: 7200s
+          statPrefix: localhost_17778
+    listenerFilters:
+    - name: envoy.filters.listener.tls_inspector
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
+    metadata:
+      filterMetadata:
+        io.kuma.tags:
+          kuma.io/service: frontend
+    name: inbound:127.0.0.1:17778
+    trafficDirection: INBOUND
diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.policy.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.policy.yaml
new file mode 100644
index 000000000..cb57bb58a
--- /dev/null
+++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-no-mtls.policy.yaml
@@ -0,0 +1,12 @@
+targetRef:
+  kind: Mesh
+from:
+  - targetRef:
+      kind: Mesh
+    default:
+      tlsVersion:
+        min: TLS12
+        max: TLS13
+      tlsCiphers:
+        - "ECDHE-ECDSA-AES128-GCM-SHA256"
+      mode: Permissive
diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.clusters.golden.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.clusters.golden.yaml
new file mode 100644
index 000000000..b9150d017
--- /dev/null
+++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.clusters.golden.yaml
@@ -0,0 +1,33 @@
+resources:
+- name: outgoing
+  resource:
+    '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
+    name: outgoing
+    transportSocket:
+      name: envoy.transport_sockets.tls
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
+        commonTlsContext:
+          alpnProtocols:
+          - kuma
+          combinedValidationContext:
+            defaultValidationContext:
+              matchTypedSubjectAltNames:
+              - matcher:
+                  exact: spiffe://default/outgoing
+                sanType: URI
+            validationContextSdsSecretConfig:
+              name: mesh_ca:secret:default
+              sdsConfig:
+                ads: {}
+                resourceApiVersion: V3
+          tlsCertificateSdsSecretConfigs:
+          - name: identity_cert:secret:default
+            sdsConfig:
+              ads: {}
+              resourceApiVersion: V3
+          tlsParams:
+            cipherSuites:
+            - ECDHE-ECDSA-AES128-GCM-SHA256
+            tlsMaximumProtocolVersion: TLSv1_3
+            tlsMinimumProtocolVersion: TLSv1_2
diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.listeners.golden.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.listeners.golden.yaml
new file mode 100644
index 000000000..eb66ed92f
--- /dev/null
+++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.listeners.golden.yaml
@@ -0,0 +1,163 @@
+resources:
+- name: inbound:127.0.0.1:17777
+  resource:
+    '@type': type.googleapis.com/envoy.config.listener.v3.Listener
+    address:
+      socketAddress:
+        address: 127.0.0.1
+        portValue: 17777
+    bindToPort: false
+    enableReusePort: false
+    filterChains:
+    - filterChainMatch:
+        transportProtocol: raw_buffer
+      filters:
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:17777
+          idleTimeout: 7200s
+          statPrefix: localhost_17777
+    - filterChainMatch:
+        transportProtocol: tls
+      filters:
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:17777
+          idleTimeout: 7200s
+          statPrefix: localhost_17777
+    - filterChainMatch:
+        applicationProtocols:
+        - kuma
+        transportProtocol: tls
+      filters:
+      - name: envoy.filters.network.rbac
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC
+          rules: {}
+          statPrefix: inbound_127_0_0_1_17777.
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:17777
+          idleTimeout: 7200s
+          statPrefix: localhost_17777
+      transportSocket:
+        name: envoy.transport_sockets.tls
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
+          commonTlsContext:
+            combinedValidationContext:
+              defaultValidationContext:
+                matchTypedSubjectAltNames:
+                - matcher:
+                    prefix: spiffe://default/
+                  sanType: URI
+              validationContextSdsSecretConfig:
+                name: mesh_ca:secret:default
+                sdsConfig:
+                  ads: {}
+                  resourceApiVersion: V3
+            tlsCertificateSdsSecretConfigs:
+            - name: identity_cert:secret:default
+              sdsConfig:
+                ads: {}
+                resourceApiVersion: V3
+            tlsParams:
+              cipherSuites:
+              - ECDHE-ECDSA-AES128-GCM-SHA256
+              tlsMaximumProtocolVersion: TLSv1_3
+              tlsMinimumProtocolVersion: TLSv1_2
+          requireClientCertificate: true
+    listenerFilters:
+    - name: envoy.filters.listener.tls_inspector
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
+    metadata:
+      filterMetadata:
+        io.kuma.tags:
+          kuma.io/service: backend
+    name: inbound:127.0.0.1:17777
+    trafficDirection: INBOUND
+- name: inbound:127.0.0.1:17778
+  resource:
+    '@type': type.googleapis.com/envoy.config.listener.v3.Listener
+    address:
+      socketAddress:
+        address: 127.0.0.1
+        portValue: 17778
+    bindToPort: false
+    enableReusePort: false
+    filterChains:
+    - filterChainMatch:
+        transportProtocol: raw_buffer
+      filters:
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:17778
+          idleTimeout: 7200s
+          statPrefix: localhost_17778
+    - filterChainMatch:
+        transportProtocol: tls
+      filters:
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:17778
+          idleTimeout: 7200s
+          statPrefix: localhost_17778
+    - filterChainMatch:
+        applicationProtocols:
+        - kuma
+        transportProtocol: tls
+      filters:
+      - name: envoy.filters.network.rbac
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC
+          rules: {}
+          statPrefix: inbound_127_0_0_1_17778.
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:17778
+          idleTimeout: 7200s
+          statPrefix: localhost_17778
+      transportSocket:
+        name: envoy.transport_sockets.tls
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
+          commonTlsContext:
+            combinedValidationContext:
+              defaultValidationContext:
+                matchTypedSubjectAltNames:
+                - matcher:
+                    prefix: spiffe://default/
+                  sanType: URI
+              validationContextSdsSecretConfig:
+                name: mesh_ca:secret:default
+                sdsConfig:
+                  ads: {}
+                  resourceApiVersion: V3
+            tlsCertificateSdsSecretConfigs:
+            - name: identity_cert:secret:default
+              sdsConfig:
+                ads: {}
+                resourceApiVersion: V3
+            tlsParams:
+              cipherSuites:
+              - ECDHE-ECDSA-AES128-GCM-SHA256
+              tlsMaximumProtocolVersion: TLSv1_3
+              tlsMinimumProtocolVersion: TLSv1_2
+          requireClientCertificate: true
+    listenerFilters:
+    - name: envoy.filters.listener.tls_inspector
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
+    metadata:
+      filterMetadata:
+        io.kuma.tags:
+          kuma.io/service: frontend
+    name: inbound:127.0.0.1:17778
+    trafficDirection: INBOUND
diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.policy.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.policy.yaml
new file mode 100644
index 000000000..cb57bb58a
--- /dev/null
+++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/permissive-with-permissive-mtls.policy.yaml
@@ -0,0 +1,12 @@
+targetRef:
+  kind: Mesh
+from:
+  - targetRef:
+      kind: Mesh
+    default:
+      tlsVersion:
+        min: TLS12
+        max: TLS13
+      tlsCiphers:
+        - "ECDHE-ECDSA-AES128-GCM-SHA256"
+      mode: Permissive
diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.clusters.golden.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.clusters.golden.yaml
new file mode 100644
index 000000000..294c7ad43
--- /dev/null
+++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.clusters.golden.yaml
@@ -0,0 +1,5 @@
+resources:
+- name: outgoing
+  resource:
+    '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
+    name: outgoing
diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.listeners.golden.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.listeners.golden.yaml
new file mode 100644
index 000000000..734cc6ddb
--- /dev/null
+++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.listeners.golden.yaml
@@ -0,0 +1,47 @@
+resources:
+- name: inbound:127.0.0.1:17777
+  resource:
+    '@type': type.googleapis.com/envoy.config.listener.v3.Listener
+    address:
+      socketAddress:
+        address: 127.0.0.1
+        portValue: 17777
+    bindToPort: false
+    enableReusePort: false
+    filterChains:
+    - filters:
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:17777
+          idleTimeout: 7200s
+          statPrefix: localhost_17777
+    metadata:
+      filterMetadata:
+        io.kuma.tags:
+          kuma.io/service: backend
+    name: inbound:127.0.0.1:17777
+    trafficDirection: INBOUND
+- name: inbound:127.0.0.1:17778
+  resource:
+    '@type': type.googleapis.com/envoy.config.listener.v3.Listener
+    address:
+      socketAddress:
+        address: 127.0.0.1
+        portValue: 17778
+    bindToPort: false
+    enableReusePort: false
+    filterChains:
+    - filters:
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:17778
+          idleTimeout: 7200s
+          statPrefix: localhost_17778
+    metadata:
+      filterMetadata:
+        io.kuma.tags:
+          kuma.io/service: frontend
+    name: inbound:127.0.0.1:17778
+    trafficDirection: INBOUND
diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.policy.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.policy.yaml
new file mode 100644
index 000000000..d8df82277
--- /dev/null
+++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-no-mtls.policy.yaml
@@ -0,0 +1,17 @@
+targetRef:
+  kind: Mesh
+from:
+  - targetRef:
+      kind: Mesh
+    default:
+      tlsVersion:
+        min: TLS11
+        max: TLS12
+      tlsCiphers:
+        - "ECDHE-ECDSA-AES128-GCM-SHA256"
+        - "ECDHE-ECDSA-AES256-GCM-SHA384"
+        - "ECDHE-ECDSA-CHACHA20-POLY1305"
+        - "ECDHE-RSA-AES128-GCM-SHA256"
+        - "ECDHE-RSA-AES256-GCM-SHA384"
+        - "ECDHE-RSA-CHACHA20-POLY1305"
+      mode: Strict
diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.clusters.golden.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.clusters.golden.yaml
new file mode 100644
index 000000000..e6db585c4
--- /dev/null
+++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.clusters.golden.yaml
@@ -0,0 +1,38 @@
+resources:
+- name: outgoing
+  resource:
+    '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
+    name: outgoing
+    transportSocket:
+      name: envoy.transport_sockets.tls
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
+        commonTlsContext:
+          alpnProtocols:
+          - kuma
+          combinedValidationContext:
+            defaultValidationContext:
+              matchTypedSubjectAltNames:
+              - matcher:
+                  exact: spiffe://default/outgoing
+                sanType: URI
+            validationContextSdsSecretConfig:
+              name: mesh_ca:secret:default
+              sdsConfig:
+                ads: {}
+                resourceApiVersion: V3
+          tlsCertificateSdsSecretConfigs:
+          - name: identity_cert:secret:default
+            sdsConfig:
+              ads: {}
+              resourceApiVersion: V3
+          tlsParams:
+            cipherSuites:
+            - ECDHE-ECDSA-AES128-GCM-SHA256
+            - ECDHE-ECDSA-AES256-GCM-SHA384
+            - ECDHE-ECDSA-CHACHA20-POLY1305
+            - ECDHE-RSA-AES128-GCM-SHA256
+            - ECDHE-RSA-AES256-GCM-SHA384
+            - ECDHE-RSA-CHACHA20-POLY1305
+            tlsMaximumProtocolVersion: TLSv1_2
+            tlsMinimumProtocolVersion: TLSv1_1
diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.listeners.golden.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.listeners.golden.yaml
new file mode 100644
index 000000000..d0c66bf4b
--- /dev/null
+++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.listeners.golden.yaml
@@ -0,0 +1,121 @@
+resources:
+- name: inbound:127.0.0.1:17777
+  resource:
+    '@type': type.googleapis.com/envoy.config.listener.v3.Listener
+    address:
+      socketAddress:
+        address: 127.0.0.1
+        portValue: 17777
+    bindToPort: false
+    enableReusePort: false
+    filterChains:
+    - filters:
+      - name: envoy.filters.network.rbac
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC
+          rules: {}
+          statPrefix: inbound_127_0_0_1_17777.
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:17777
+          idleTimeout: 7200s
+          statPrefix: localhost_17777
+      transportSocket:
+        name: envoy.transport_sockets.tls
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
+          commonTlsContext:
+            combinedValidationContext:
+              defaultValidationContext:
+                matchTypedSubjectAltNames:
+                - matcher:
+                    prefix: spiffe://default/
+                  sanType: URI
+              validationContextSdsSecretConfig:
+                name: mesh_ca:secret:default
+                sdsConfig:
+                  ads: {}
+                  resourceApiVersion: V3
+            tlsCertificateSdsSecretConfigs:
+            - name: identity_cert:secret:default
+              sdsConfig:
+                ads: {}
+                resourceApiVersion: V3
+            tlsParams:
+              cipherSuites:
+              - ECDHE-ECDSA-AES128-GCM-SHA256
+              - ECDHE-ECDSA-AES256-GCM-SHA384
+              - ECDHE-ECDSA-CHACHA20-POLY1305
+              - ECDHE-RSA-AES128-GCM-SHA256
+              - ECDHE-RSA-AES256-GCM-SHA384
+              - ECDHE-RSA-CHACHA20-POLY1305
+              tlsMaximumProtocolVersion: TLSv1_2
+              tlsMinimumProtocolVersion: TLSv1_1
+          requireClientCertificate: true
+    metadata:
+      filterMetadata:
+        io.kuma.tags:
+          kuma.io/service: backend
+    name: inbound:127.0.0.1:17777
+    trafficDirection: INBOUND
+- name: inbound:127.0.0.1:17778
+  resource:
+    '@type': type.googleapis.com/envoy.config.listener.v3.Listener
+    address:
+      socketAddress:
+        address: 127.0.0.1
+        portValue: 17778
+    bindToPort: false
+    enableReusePort: false
+    filterChains:
+    - filters:
+      - name: envoy.filters.network.rbac
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC
+          rules: {}
+          statPrefix: inbound_127_0_0_1_17778.
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:17778
+          idleTimeout: 7200s
+          statPrefix: localhost_17778
+      transportSocket:
+        name: envoy.transport_sockets.tls
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
+          commonTlsContext:
+            combinedValidationContext:
+              defaultValidationContext:
+                matchTypedSubjectAltNames:
+                - matcher:
+                    prefix: spiffe://default/
+                  sanType: URI
+              validationContextSdsSecretConfig:
+                name: mesh_ca:secret:default
+                sdsConfig:
+                  ads: {}
+                  resourceApiVersion: V3
+            tlsCertificateSdsSecretConfigs:
+            - name: identity_cert:secret:default
+              sdsConfig:
+                ads: {}
+                resourceApiVersion: V3
+            tlsParams:
+              cipherSuites:
+              - ECDHE-ECDSA-AES128-GCM-SHA256
+              - ECDHE-ECDSA-AES256-GCM-SHA384
+              - ECDHE-ECDSA-CHACHA20-POLY1305
+              - ECDHE-RSA-AES128-GCM-SHA256
+              - ECDHE-RSA-AES256-GCM-SHA384
+              - ECDHE-RSA-CHACHA20-POLY1305
+              tlsMaximumProtocolVersion: TLSv1_2
+              tlsMinimumProtocolVersion: TLSv1_1
+          requireClientCertificate: true
+    metadata:
+      filterMetadata:
+        io.kuma.tags:
+          kuma.io/service: frontend
+    name: inbound:127.0.0.1:17778
+    trafficDirection: INBOUND
diff --git a/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.policy.yaml b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.policy.yaml
new file mode 100644
index 000000000..d8df82277
--- /dev/null
+++ b/pkg/plugins/policies/meshtls/plugin/v1alpha1/testdata/strict-with-permissive-mtls.policy.yaml
@@ -0,0 +1,17 @@
+targetRef:
+  kind: Mesh
+from:
+  - targetRef:
+      kind: Mesh
+    default:
+      tlsVersion:
+        min: TLS11
+        max: TLS12
+      tlsCiphers:
+        - "ECDHE-ECDSA-AES128-GCM-SHA256"
+        - "ECDHE-ECDSA-AES256-GCM-SHA384"
+        - "ECDHE-ECDSA-CHACHA20-POLY1305"
+        - "ECDHE-RSA-AES128-GCM-SHA256"
+        - "ECDHE-RSA-AES256-GCM-SHA384"
+        - "ECDHE-RSA-CHACHA20-POLY1305"
+      mode: Strict
diff --git a/pkg/plugins/policies/meshtrafficpermission/plugin/v1alpha1/plugin_test.go b/pkg/plugins/policies/meshtrafficpermission/plugin/v1alpha1/plugin_test.go
index 18d1702d8..507381acc 100644
--- a/pkg/plugins/policies/meshtrafficpermission/plugin/v1alpha1/plugin_test.go
+++ b/pkg/plugins/policies/meshtrafficpermission/plugin/v1alpha1/plugin_test.go
@@ -38,7 +38,7 @@ var _ = Describe("RBAC", func() {
 			listener, err := listeners.NewInboundListenerBuilder(envoy.APIV3, "192.168.0.1", 8080, core_xds.SocketAddressProtocolTCP).
 				WithOverwriteName("test_listener").
 				Configure(listeners.FilterChain(listeners.NewFilterChainBuilder(envoy.APIV3, envoy.AnonymousResource).
-					Configure(listeners.ServerSideMTLS(ctx.Mesh.Resource, envoy.NewSecretsTracker(ctx.Mesh.Resource.Meta.GetName(), nil))).
+					Configure(listeners.ServerSideMTLS(ctx.Mesh.Resource, envoy.NewSecretsTracker(ctx.Mesh.Resource.Meta.GetName(), nil), nil, nil)).
 					Configure(listeners.HttpConnectionManager("test_listener", false)))).
 				Build()
 			Expect(err).ToNot(HaveOccurred())
@@ -52,7 +52,7 @@ var _ = Describe("RBAC", func() {
 			listener2, err := listeners.NewInboundListenerBuilder(envoy.APIV3, "192.168.0.1", 8081, core_xds.SocketAddressProtocolTCP).
 				WithOverwriteName("test_listener2").
 				Configure(listeners.FilterChain(listeners.NewFilterChainBuilder(envoy.APIV3, envoy.AnonymousResource).
-					Configure(listeners.ServerSideMTLS(ctx.Mesh.Resource, envoy.NewSecretsTracker(ctx.Mesh.Resource.Meta.GetName(), nil))).
+					Configure(listeners.ServerSideMTLS(ctx.Mesh.Resource, envoy.NewSecretsTracker(ctx.Mesh.Resource.Meta.GetName(), nil), nil, nil)).
 					Configure(listeners.HttpConnectionManager("test_listener2", false)))).
 				Build()
 			Expect(err).ToNot(HaveOccurred())
@@ -66,7 +66,7 @@ var _ = Describe("RBAC", func() {
 			listener3, err := listeners.NewInboundListenerBuilder(envoy.APIV3, "192.168.0.1", 8082, core_xds.SocketAddressProtocolTCP).
 				WithOverwriteName("test_listener3").
 				Configure(listeners.FilterChain(listeners.NewFilterChainBuilder(envoy.APIV3, envoy.AnonymousResource).
-					Configure(listeners.ServerSideMTLS(ctx.Mesh.Resource, envoy.NewSecretsTracker(ctx.Mesh.Resource.Meta.GetName(), nil))).
+					Configure(listeners.ServerSideMTLS(ctx.Mesh.Resource, envoy.NewSecretsTracker(ctx.Mesh.Resource.Meta.GetName(), nil), nil, nil)).
 					Configure(listeners.HttpConnectionManager("test_listener3", false)))).
 				Build()
 			Expect(err).ToNot(HaveOccurred())
diff --git a/pkg/xds/envoy/clusters/v3/client_side_tls_configurer_test.go b/pkg/xds/envoy/clusters/v3/client_side_tls_configurer_test.go
index 7a84cd835..d38cc6f58 100644
--- a/pkg/xds/envoy/clusters/v3/client_side_tls_configurer_test.go
+++ b/pkg/xds/envoy/clusters/v3/client_side_tls_configurer_test.go
@@ -1,6 +1,7 @@
 package clusters_test
 
 import (
+	tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 
@@ -126,8 +127,8 @@ var _ = Describe("ClientSideTLSConfigurer", func() {
 						ClientKey:          []byte("clientkey"),
 						AllowRenegotiation: true,
 						ServerName:         "custom",
-						MinTlsVersion:      pointer.To(xds.TLSVersion10),
-						MaxTlsVersion:      pointer.To(xds.TLSVersion13),
+						MinTlsVersion:      pointer.To(tlsv3.TlsParameters_TLSv1_0),
+						MaxTlsVersion:      pointer.To(tlsv3.TlsParameters_TLSv1_3),
 					},
 				},
 			},
diff --git a/pkg/xds/envoy/listeners/v3/server_mtls_configurer_test.go b/pkg/xds/envoy/listeners/v3/server_mtls_configurer_test.go
index c30cf66b8..9eb0dd25f 100644
--- a/pkg/xds/envoy/listeners/v3/server_mtls_configurer_test.go
+++ b/pkg/xds/envoy/listeners/v3/server_mtls_configurer_test.go
@@ -30,7 +30,7 @@ var _ = Describe("ServerMtlsConfigurer", func() {
 			tracker := envoy_common.NewSecretsTracker(given.mesh.GetMeta().GetName(), nil)
 			listener, err := NewInboundListenerBuilder(envoy_common.APIV3, given.listenerAddress, given.listenerPort, given.listenerProtocol).
 				Configure(FilterChain(NewFilterChainBuilder(envoy_common.APIV3, envoy_common.AnonymousResource).
-					Configure(ServerSideMTLS(given.mesh, tracker)).
+					Configure(ServerSideMTLS(given.mesh, tracker, nil, nil)).
 					Configure(TcpProxyDeprecated(given.statsName, given.clusters...)))).
 				Build()
 			// then
diff --git a/pkg/xds/generator/inbound_proxy_generator_test.go b/pkg/xds/generator/inbound_proxy_generator_test.go
index 90fe2953a..2ac37a007 100644
--- a/pkg/xds/generator/inbound_proxy_generator_test.go
+++ b/pkg/xds/generator/inbound_proxy_generator_test.go
@@ -250,5 +250,10 @@ var _ = Describe("InboundProxyGenerator", func() {
 			expected:      "6-envoy-config.golden.yaml",
 			mode:          mesh_proto.CertificateAuthorityBackend_PERMISSIVE,
 		}),
+		Entry("07. transparent_proxying=true, ip_addresses=2, ports=2, mode=strict", testCase{
+			dataplaneFile: "7-dataplane.input.yaml",
+			expected:      "7-envoy-config.golden.yaml",
+			mode:          mesh_proto.CertificateAuthorityBackend_STRICT,
+		}),
 	)
 })
diff --git a/pkg/xds/generator/testdata/inbound-proxy/7-dataplane.input.yaml b/pkg/xds/generator/testdata/inbound-proxy/7-dataplane.input.yaml
new file mode 100644
index 000000000..8b5af1b8c
--- /dev/null
+++ b/pkg/xds/generator/testdata/inbound-proxy/7-dataplane.input.yaml
@@ -0,0 +1,36 @@
+networking:
+  transparentProxying:
+    redirectPortOutbound: 15001
+    redirectPortInbound: 15006
+  address: 192.168.0.1
+  inbound:
+    - port: 80
+      servicePort: 8080
+      tags:
+        kuma.io/service: backend1
+        kuma.io/protocol: http
+    - port: 443
+      servicePort: 8443
+      tags:
+        kuma.io/service: backend2
+    - address: 192.168.0.2
+      port: 80
+      servicePort: 8080
+      tags:
+        kuma.io/service: backend3
+        kuma.io/protocol: http
+    - address: 192.168.0.2
+      port: 443
+      servicePort: 8443
+      tags:
+        kuma.io/service: backend4
+    - address: 127.0.0.1
+      port: 8080
+      servicePort: 1234
+      tags:
+        kuma.io/service: loopback
+    - address: ::1
+      port: 8081
+      servicePort: 12345
+      tags:
+        kuma.io/service: loopbackv6
diff --git a/pkg/xds/generator/testdata/inbound-proxy/7-envoy-config.golden.yaml b/pkg/xds/generator/testdata/inbound-proxy/7-envoy-config.golden.yaml
new file mode 100644
index 000000000..444c5a4e3
--- /dev/null
+++ b/pkg/xds/generator/testdata/inbound-proxy/7-envoy-config.golden.yaml
@@ -0,0 +1,513 @@
+resources:
+- name: localhost:1234
+  resource:
+    '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
+    altStatName: localhost_1234
+    connectTimeout: 10s
+    loadAssignment:
+      clusterName: localhost:1234
+      endpoints:
+      - lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: 127.0.0.1
+                portValue: 1234
+    name: localhost:1234
+    type: STATIC
+- name: localhost:12345
+  resource:
+    '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
+    altStatName: localhost_12345
+    connectTimeout: 10s
+    loadAssignment:
+      clusterName: localhost:12345
+      endpoints:
+      - lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: ::1
+                portValue: 12345
+    name: localhost:12345
+    type: STATIC
+- name: localhost:8080
+  resource:
+    '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
+    altStatName: localhost_8080
+    connectTimeout: 10s
+    loadAssignment:
+      clusterName: localhost:8080
+      endpoints:
+      - lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: 192.168.0.2
+                portValue: 8080
+    name: localhost:8080
+    type: STATIC
+    typedExtensionProtocolOptions:
+      envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+        '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+        commonHttpProtocolOptions:
+          idleTimeout: 7200s
+        explicitHttpConfig:
+          httpProtocolOptions: {}
+    upstreamBindConfig:
+      sourceAddress:
+        address: 127.0.0.6
+        portValue: 0
+- name: localhost:8443
+  resource:
+    '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
+    altStatName: localhost_8443
+    connectTimeout: 10s
+    loadAssignment:
+      clusterName: localhost:8443
+      endpoints:
+      - lbEndpoints:
+        - endpoint:
+            address:
+              socketAddress:
+                address: 192.168.0.2
+                portValue: 8443
+    name: localhost:8443
+    type: STATIC
+    upstreamBindConfig:
+      sourceAddress:
+        address: 127.0.0.6
+        portValue: 0
+- name: inbound:127.0.0.1:8080
+  resource:
+    '@type': type.googleapis.com/envoy.config.listener.v3.Listener
+    address:
+      socketAddress:
+        address: 127.0.0.1
+        portValue: 8080
+    bindToPort: false
+    enableReusePort: false
+    filterChains:
+    - filters:
+      - name: envoy.filters.network.rbac
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC
+          rules: {}
+          statPrefix: inbound_127_0_0_1_8080.
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:1234
+          idleTimeout: 7200s
+          statPrefix: localhost_1234
+      transportSocket:
+        name: envoy.transport_sockets.tls
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
+          commonTlsContext:
+            combinedValidationContext:
+              defaultValidationContext:
+                matchTypedSubjectAltNames:
+                - matcher:
+                    prefix: spiffe://default/
+                  sanType: URI
+              validationContextSdsSecretConfig:
+                name: mesh_ca:secret:default
+                sdsConfig:
+                  ads: {}
+                  resourceApiVersion: V3
+            tlsCertificateSdsSecretConfigs:
+            - name: identity_cert:secret:default
+              sdsConfig:
+                ads: {}
+                resourceApiVersion: V3
+          requireClientCertificate: true
+    metadata:
+      filterMetadata:
+        io.kuma.tags:
+          kuma.io/service: loopback
+    name: inbound:127.0.0.1:8080
+    trafficDirection: INBOUND
+- name: inbound:192.168.0.1:443
+  resource:
+    '@type': type.googleapis.com/envoy.config.listener.v3.Listener
+    address:
+      socketAddress:
+        address: 192.168.0.1
+        portValue: 443
+    bindToPort: false
+    enableReusePort: false
+    filterChains:
+    - filters:
+      - name: envoy.filters.network.rbac
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC
+          rules: {}
+          statPrefix: inbound_192_168_0_1_443.
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:8443
+          idleTimeout: 7200s
+          statPrefix: localhost_8443
+      transportSocket:
+        name: envoy.transport_sockets.tls
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
+          commonTlsContext:
+            combinedValidationContext:
+              defaultValidationContext:
+                matchTypedSubjectAltNames:
+                - matcher:
+                    prefix: spiffe://default/
+                  sanType: URI
+              validationContextSdsSecretConfig:
+                name: mesh_ca:secret:default
+                sdsConfig:
+                  ads: {}
+                  resourceApiVersion: V3
+            tlsCertificateSdsSecretConfigs:
+            - name: identity_cert:secret:default
+              sdsConfig:
+                ads: {}
+                resourceApiVersion: V3
+          requireClientCertificate: true
+    metadata:
+      filterMetadata:
+        io.kuma.tags:
+          kuma.io/service: backend2
+    name: inbound:192.168.0.1:443
+    trafficDirection: INBOUND
+- name: inbound:192.168.0.1:80
+  resource:
+    '@type': type.googleapis.com/envoy.config.listener.v3.Listener
+    address:
+      socketAddress:
+        address: 192.168.0.1
+        portValue: 80
+    bindToPort: false
+    enableReusePort: false
+    filterChains:
+    - filters:
+      - name: envoy.filters.network.rbac
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC
+          rules:
+            policies:
+              tp-1:
+                permissions:
+                - any: true
+                principals:
+                - andIds:
+                    ids:
+                    - authenticated:
+                        principalName:
+                          exact: kuma://version/1.0
+                    - authenticated:
+                        principalName:
+                          exact: spiffe://default/web1
+          statPrefix: inbound_192_168_0_1_80.
+      - name: envoy.filters.network.http_connection_manager
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+          commonHttpProtocolOptions:
+            idleTimeout: 7200s
+          forwardClientCertDetails: SANITIZE_SET
+          httpFilters:
+          - name: envoy.filters.http.fault
+            typedConfig:
+              '@type': type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
+              delay:
+                fixedDelay: 5s
+                percentage:
+                  numerator: 50
+              headers:
+              - name: x-kuma-tags
+                safeRegexMatch:
+                  regex: .*&kuma.io/service=[^&]*frontend[,&].*
+          - name: envoy.filters.http.local_ratelimit
+            typedConfig:
+              '@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:
+            - x-kuma-tags
+            validateClusters: false
+            virtualHosts:
+            - domains:
+              - '*'
+              name: backend1
+              routes:
+              - match:
+                  headers:
+                  - name: x-kuma-tags
+                    safeRegexMatch:
+                      regex: .*&kuma.io/service=[^&]*frontend[,&].*
+                  prefix: /
+                route:
+                  cluster: localhost:8080
+                  timeout: 0s
+                typedPerFilterConfig:
+                  envoy.filters.http.local_ratelimit:
+                    '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
+                    filterEnabled:
+                      defaultValue:
+                        numerator: 100
+                      runtimeKey: local_rate_limit_enabled
+                    filterEnforced:
+                      defaultValue:
+                        numerator: 100
+                      runtimeKey: local_rate_limit_enforced
+                    statPrefix: rate_limit
+                    tokenBucket:
+                      fillInterval: 10s
+                      maxTokens: 200
+                      tokensPerFill: 200
+              - match:
+                  headers:
+                  - name: x-kuma-tags
+                    safeRegexMatch:
+                      regex: .*&kuma.io/service=.*
+                  prefix: /
+                route:
+                  cluster: localhost:8080
+                  timeout: 0s
+                typedPerFilterConfig:
+                  envoy.filters.http.local_ratelimit:
+                    '@type': type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
+                    filterEnabled:
+                      defaultValue:
+                        numerator: 100
+                      runtimeKey: local_rate_limit_enabled
+                    filterEnforced:
+                      defaultValue:
+                        numerator: 100
+                      runtimeKey: local_rate_limit_enforced
+                    responseHeadersToAdd:
+                    - appendAction: OVERWRITE_IF_EXISTS_OR_ADD
+                      header:
+                        key: x-rate-limited
+                        value: "true"
+                    statPrefix: rate_limit
+                    status:
+                      code: NotFound
+                    tokenBucket:
+                      fillInterval: 2s
+                      maxTokens: 100
+                      tokensPerFill: 100
+              - match:
+                  prefix: /
+                route:
+                  cluster: localhost:8080
+                  timeout: 0s
+          setCurrentClientCertDetails:
+            uri: true
+          statPrefix: localhost_8080
+          streamIdleTimeout: 3600s
+      transportSocket:
+        name: envoy.transport_sockets.tls
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
+          commonTlsContext:
+            combinedValidationContext:
+              defaultValidationContext:
+                matchTypedSubjectAltNames:
+                - matcher:
+                    prefix: spiffe://default/
+                  sanType: URI
+              validationContextSdsSecretConfig:
+                name: mesh_ca:secret:default
+                sdsConfig:
+                  ads: {}
+                  resourceApiVersion: V3
+            tlsCertificateSdsSecretConfigs:
+            - name: identity_cert:secret:default
+              sdsConfig:
+                ads: {}
+                resourceApiVersion: V3
+          requireClientCertificate: true
+    metadata:
+      filterMetadata:
+        io.kuma.tags:
+          kuma.io/protocol: http
+          kuma.io/service: backend1
+    name: inbound:192.168.0.1:80
+    trafficDirection: INBOUND
+- name: inbound:192.168.0.2:443
+  resource:
+    '@type': type.googleapis.com/envoy.config.listener.v3.Listener
+    address:
+      socketAddress:
+        address: 192.168.0.2
+        portValue: 443
+    bindToPort: false
+    enableReusePort: false
+    filterChains:
+    - filters:
+      - name: envoy.filters.network.rbac
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC
+          rules: {}
+          statPrefix: inbound_192_168_0_2_443.
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:8443
+          idleTimeout: 7200s
+          statPrefix: localhost_8443
+      transportSocket:
+        name: envoy.transport_sockets.tls
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
+          commonTlsContext:
+            combinedValidationContext:
+              defaultValidationContext:
+                matchTypedSubjectAltNames:
+                - matcher:
+                    prefix: spiffe://default/
+                  sanType: URI
+              validationContextSdsSecretConfig:
+                name: mesh_ca:secret:default
+                sdsConfig:
+                  ads: {}
+                  resourceApiVersion: V3
+            tlsCertificateSdsSecretConfigs:
+            - name: identity_cert:secret:default
+              sdsConfig:
+                ads: {}
+                resourceApiVersion: V3
+          requireClientCertificate: true
+    metadata:
+      filterMetadata:
+        io.kuma.tags:
+          kuma.io/service: backend4
+    name: inbound:192.168.0.2:443
+    trafficDirection: INBOUND
+- name: inbound:192.168.0.2:80
+  resource:
+    '@type': type.googleapis.com/envoy.config.listener.v3.Listener
+    address:
+      socketAddress:
+        address: 192.168.0.2
+        portValue: 80
+    bindToPort: false
+    enableReusePort: false
+    filterChains:
+    - filters:
+      - name: envoy.filters.network.rbac
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC
+          rules: {}
+          statPrefix: inbound_192_168_0_2_80.
+      - name: envoy.filters.network.http_connection_manager
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+          commonHttpProtocolOptions:
+            idleTimeout: 7200s
+          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:
+            - x-kuma-tags
+            validateClusters: false
+            virtualHosts:
+            - domains:
+              - '*'
+              name: backend3
+              routes:
+              - match:
+                  prefix: /
+                route:
+                  cluster: localhost:8080
+                  timeout: 0s
+          setCurrentClientCertDetails:
+            uri: true
+          statPrefix: localhost_8080
+          streamIdleTimeout: 3600s
+      transportSocket:
+        name: envoy.transport_sockets.tls
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
+          commonTlsContext:
+            combinedValidationContext:
+              defaultValidationContext:
+                matchTypedSubjectAltNames:
+                - matcher:
+                    prefix: spiffe://default/
+                  sanType: URI
+              validationContextSdsSecretConfig:
+                name: mesh_ca:secret:default
+                sdsConfig:
+                  ads: {}
+                  resourceApiVersion: V3
+            tlsCertificateSdsSecretConfigs:
+            - name: identity_cert:secret:default
+              sdsConfig:
+                ads: {}
+                resourceApiVersion: V3
+          requireClientCertificate: true
+    metadata:
+      filterMetadata:
+        io.kuma.tags:
+          kuma.io/protocol: http
+          kuma.io/service: backend3
+    name: inbound:192.168.0.2:80
+    trafficDirection: INBOUND
+- name: inbound:[::1]:8081
+  resource:
+    '@type': type.googleapis.com/envoy.config.listener.v3.Listener
+    address:
+      socketAddress:
+        address: ::1
+        portValue: 8081
+    bindToPort: false
+    enableReusePort: false
+    filterChains:
+    - filters:
+      - name: envoy.filters.network.rbac
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC
+          rules: {}
+          statPrefix: inbound____1__8081.
+      - name: envoy.filters.network.tcp_proxy
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
+          cluster: localhost:12345
+          idleTimeout: 7200s
+          statPrefix: localhost_12345
+      transportSocket:
+        name: envoy.transport_sockets.tls
+        typedConfig:
+          '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
+          commonTlsContext:
+            combinedValidationContext:
+              defaultValidationContext:
+                matchTypedSubjectAltNames:
+                - matcher:
+                    prefix: spiffe://default/
+                  sanType: URI
+              validationContextSdsSecretConfig:
+                name: mesh_ca:secret:default
+                sdsConfig:
+                  ads: {}
+                  resourceApiVersion: V3
+            tlsCertificateSdsSecretConfigs:
+            - name: identity_cert:secret:default
+              sdsConfig:
+                ads: {}
+                resourceApiVersion: V3
+          requireClientCertificate: true
+    metadata:
+      filterMetadata:
+        io.kuma.tags:
+          kuma.io/service: loopbackv6
+    name: inbound:[::1]:8081
+    trafficDirection: INBOUND
diff --git a/test/e2e_env/multizone/meshtls/meshtls.go b/test/e2e_env/multizone/meshtls/meshtls.go
new file mode 100644
index 000000000..3724643e4
--- /dev/null
+++ b/test/e2e_env/multizone/meshtls/meshtls.go
@@ -0,0 +1,107 @@
+package meshtls
+
+import (
+	"fmt"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	meshtls_api "github.com/kumahq/kuma/pkg/plugins/policies/meshtls/api/v1alpha1"
+	. "github.com/kumahq/kuma/test/framework"
+	framework_client "github.com/kumahq/kuma/test/framework/client"
+	"github.com/kumahq/kuma/test/framework/deployments/democlient"
+	"github.com/kumahq/kuma/test/framework/deployments/testserver"
+	"github.com/kumahq/kuma/test/framework/envs/multizone"
+)
+
+func MeshTLS() {
+	const meshName = "multizone-meshtls"
+	const k8sZoneNamespace = "multizone-meshtls"
+
+	BeforeAll(func() {
+		// Global
+		Expect(NewClusterSetup().
+			Install(MTLSMeshUniversal(meshName)).
+			Install(MeshTrafficPermissionAllowAllUniversal(meshName)).
+			Setup(multizone.Global)).To(Succeed())
+		Expect(WaitForMesh(meshName, multizone.Zones())).To(Succeed())
+
+		// Kube Zone 1
+		Expect(NewClusterSetup().
+			Install(NamespaceWithSidecarInjection(k8sZoneNamespace)).
+			Install(testserver.Install(
+				testserver.WithName("test-server"),
+				testserver.WithMesh(meshName),
+				testserver.WithNamespace(k8sZoneNamespace),
+				testserver.WithEchoArgs("echo", "--instance", "kube-test-server-1"),
+			)).
+			Setup(multizone.KubeZone1),
+		).To(Succeed())
+
+		Expect(NewClusterSetup().
+			Install(NamespaceWithSidecarInjection(k8sZoneNamespace)).
+			Install(democlient.Install(
+				democlient.WithName("demo-client"),
+				democlient.WithMesh(meshName),
+				democlient.WithNamespace(k8sZoneNamespace),
+			)).
+			Setup(multizone.KubeZone2),
+		).To(Succeed())
+	})
+
+	AfterEachFailure(func() {
+		DebugUniversal(multizone.Global, meshName)
+		DebugKube(multizone.KubeZone1, meshName, k8sZoneNamespace)
+		DebugKube(multizone.KubeZone2, meshName, k8sZoneNamespace)
+	})
+
+	E2EAfterEach(func() {
+		Expect(DeleteMeshResources(multizone.Global, meshName, meshtls_api.MeshTLSResourceTypeDescriptor)).To(Succeed())
+	})
+
+	E2EAfterAll(func() {
+		Expect(multizone.KubeZone1.TriggerDeleteNamespace(k8sZoneNamespace)).To(Succeed())
+		Expect(multizone.KubeZone2.TriggerDeleteNamespace(k8sZoneNamespace)).To(Succeed())
+		Expect(multizone.Global.DeleteMesh(meshName)).To(Succeed())
+	})
+
+	It("should define TLS version and traffic should works", func() {
+		policy := fmt.Sprintf(`
+type: MeshTLS
+mesh: %s
+name: mesh-tls-policy
+spec:
+  targetRef:
+    kind: Mesh
+  from:
+    - targetRef:
+        kind: Mesh
+      default:
+        tlsVersion:
+          min: TLS13
+          max: TLS13`, meshName)
+
+		Eventually(func(g Gomega) {
+			resp, err := framework_client.CollectEchoResponse(
+				multizone.KubeZone2, "demo-client", "test-server_multizone-meshtls_svc_80.mesh",
+				framework_client.FromKubernetesPod(k8sZoneNamespace, "demo-client"),
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(resp.Instance).To(Equal("kube-test-server-1"))
+		}, "30s", "1s").MustPassRepeatedly(5).Should(Succeed())
+
+		// when
+		Expect(multizone.Global.Install(YamlUniversal(policy))).To(Succeed())
+
+		// then
+		// traffic should still works
+		Eventually(func(g Gomega) {
+			resp, err := framework_client.CollectEchoResponse(
+				multizone.KubeZone2, "demo-client", "test-server_multizone-meshtls_svc_80.mesh",
+				framework_client.FromKubernetesPod(k8sZoneNamespace, "demo-client"),
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(resp.Instance).To(Equal("kube-test-server-1"))
+		}, "30s", "1s").MustPassRepeatedly(5).Should(Succeed())
+	})
+}
diff --git a/test/e2e_env/multizone/multizone_suite_test.go b/test/e2e_env/multizone/multizone_suite_test.go
index 4b935541d..d0a5e9e48 100644
--- a/test/e2e_env/multizone/multizone_suite_test.go
+++ b/test/e2e_env/multizone/multizone_suite_test.go
@@ -19,6 +19,7 @@ import (
 	"github.com/kumahq/kuma/test/e2e_env/multizone/meshservice"
 	"github.com/kumahq/kuma/test/e2e_env/multizone/meshtcproute"
 	"github.com/kumahq/kuma/test/e2e_env/multizone/meshtimeout"
+	"github.com/kumahq/kuma/test/e2e_env/multizone/meshtls"
 	"github.com/kumahq/kuma/test/e2e_env/multizone/meshtrafficpermission"
 	"github.com/kumahq/kuma/test/e2e_env/multizone/ownership"
 	"github.com/kumahq/kuma/test/e2e_env/multizone/reachablebackends"
@@ -80,4 +81,5 @@ var (
 	_ = Describe("Available services", connectivity.AvailableServices, Ordered)
 	_ = Describe("ReachableBackends", reachablebackends.ReachableBackends, Ordered)
 	_ = Describe("MeshServiceReachableBackends", reachablebackends.MeshServicesWithReachableBackendsOption, Ordered)
+	_ = Describe("MeshTLS", meshtls.MeshTLS, Ordered)
 )
diff --git a/test/e2e_env/multizone/reachablebackends/reachablebackends_meshservices.go b/test/e2e_env/multizone/reachablebackends/reachablebackends_meshservices.go
index 01d8811d5..bc66156bd 100644
--- a/test/e2e_env/multizone/reachablebackends/reachablebackends_meshservices.go
+++ b/test/e2e_env/multizone/reachablebackends/reachablebackends_meshservices.go
@@ -28,7 +28,7 @@ func MeshServicesWithReachableBackendsOption() {
 type: Mesh
 name: "%s"
 meshServices:
-  enabled: ReachableBackendRefs
+  enabled: ReachableBackends
 mtls:
   enabledBackend: ca-1
   backends:
diff --git a/test/e2e_env/universal/meshtls/policy.go b/test/e2e_env/universal/meshtls/policy.go
new file mode 100644
index 000000000..dd9811e23
--- /dev/null
+++ b/test/e2e_env/universal/meshtls/policy.go
@@ -0,0 +1,416 @@
+package meshtls
+
+import (
+	"fmt"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/kumahq/kuma/pkg/test/resources/samples"
+	. "github.com/kumahq/kuma/test/framework"
+	"github.com/kumahq/kuma/test/framework/client"
+	"github.com/kumahq/kuma/test/framework/envoy_admin/stats"
+	"github.com/kumahq/kuma/test/framework/envs/universal"
+)
+
+func Policy() {
+	var testServerContainerName string
+	var testServer2ContainerName string
+	meshName := "mesh-tls"
+	testServerName := "mesh-tls-test-server"
+	testServer2Name := "mesh-tls-test-server-2"
+
+	BeforeAll(func() {
+		testServerContainerName = fmt.Sprintf("%s_%s", universal.Cluster.Name(), testServerName)
+		testServer2ContainerName = fmt.Sprintf("%s_%s", universal.Cluster.Name(), testServer2Name)
+		Expect(NewClusterSetup().
+			Install(ResourceUniversal(samples.MeshMTLSBuilder().WithName(meshName).Build())).
+			Install(MeshTrafficPermissionAllowAllUniversal(meshName)).
+			Install(TestServerUniversal(
+				testServerName, meshName,
+				WithArgs([]string{"echo", "--instance", "test-server"}),
+				WithServiceName("mesh-tls-test-server"),
+				WithDockerContainerName(testServerContainerName),
+			)).
+			Install(TestServerUniversal(
+				testServer2Name, meshName,
+				WithArgs([]string{"echo", "--instance", "test-server-2"}),
+				WithServiceName("mesh-tls-test-server-2"),
+				WithDockerContainerName(testServer2ContainerName),
+			)).
+			Install(DemoClientUniversal("mesh-tls-demo-client", meshName, WithTransparentProxy(true))).
+			Install(DemoClientUniversal("mesh-tls-demo-client-no-mesh", "", WithoutDataplane())).
+			Setup(universal.Cluster)).To(Succeed())
+	})
+
+	AfterEachFailure(func() {
+		DebugUniversal(universal.Cluster, meshName)
+	})
+
+	E2EAfterAll(func() {
+		Expect(universal.Cluster.DeleteMeshApps(meshName)).To(Succeed())
+		Expect(universal.Cluster.DeleteMesh(meshName)).To(Succeed())
+	})
+
+	It("should change single dataplane to Permissive", func() {
+		policy := fmt.Sprintf(`
+type: MeshTLS
+mesh: %s
+name: mesh-tls-policy
+spec:
+  targetRef:
+    kind: MeshSubset
+    tags:
+      kuma.io/service: %s
+  from:
+    - targetRef:
+        kind: Mesh
+      default:
+        mode: Permissive`, meshName, testServerName)
+		// when
+		// default strict mode on mesh
+		Expect(universal.Cluster.Install(
+			ResourceUniversal(samples.MeshMTLSBuilder().WithName(meshName).Build()),
+		)).To(Succeed())
+
+		// then
+		// can access test-server from service in the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectEchoResponse(
+				universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server.mesh",
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Instance).To(Equal("test-server"))
+		}, "30s", "500ms").Should(Succeed())
+
+		// and
+		// can access test-server-2 from service in the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectEchoResponse(
+				universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server-2.mesh",
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Instance).To(Equal("test-server-2"))
+		}, "30s", "500ms").Should(Succeed())
+
+		// and
+		// cannot access test-server from service outside of the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectFailure(
+				universal.Cluster, "mesh-tls-demo-client-no-mesh", testServerContainerName,
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Exitcode).To(Equal(52))
+		}, "30s", "500ms").Should(Succeed())
+
+		// and
+		// cannot access test-server-2 from service outside of the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectFailure(
+				universal.Cluster, "mesh-tls-demo-client-no-mesh", testServer2ContainerName,
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Exitcode).To(Equal(52))
+		}, "30s", "500ms").Should(Succeed())
+
+		// when
+		// applied MeshTLS policy to set Permissive mode on test-server
+		Expect(universal.Cluster.Install(YamlUniversal(policy))).To(Succeed())
+
+		// then
+		// can access test-server from service in the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectEchoResponse(
+				universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server.mesh",
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Instance).To(Equal("test-server"))
+		}, "30s", "500ms").Should(Succeed())
+
+		// and
+		// can access test-server-2 from service in the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectEchoResponse(
+				universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server-2.mesh",
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Instance).To(Equal("test-server-2"))
+		}, "30s", "500ms").Should(Succeed())
+
+		// and
+		// can access test-server from service outside of the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectEchoResponse(
+				universal.Cluster, "mesh-tls-demo-client-no-mesh", testServerContainerName,
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Instance).To(Equal("test-server"))
+		}, "30s", "500ms").Should(Succeed())
+
+		// and
+		// cannot access test-server-2 from service outside of the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectFailure(
+				universal.Cluster, "mesh-tls-demo-client-no-mesh", testServer2ContainerName,
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Exitcode).To(Equal(52))
+		}, "30s", "500ms").Should(Succeed())
+	})
+
+	It("should change single dataplane to Strict", func() {
+		policy := fmt.Sprintf(`
+type: MeshTLS
+mesh: %s
+name: mesh-tls-policy
+spec:
+  targetRef:
+    kind: MeshSubset
+    tags:
+      kuma.io/service: %s
+  from:
+    - targetRef:
+        kind: Mesh
+      default:
+        mode: Strict`, meshName, testServerName)
+		// when
+		// default strict mode on mesh
+		Expect(universal.Cluster.Install(
+			ResourceUniversal(samples.MeshMTLSBuilder().WithPermissiveMTLSBackends().WithName(meshName).Build()),
+		)).To(Succeed())
+
+		// then
+		// can access test-server from service in the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectEchoResponse(
+				universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server.mesh",
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Instance).To(Equal("test-server"))
+		}, "30s", "500ms").Should(Succeed())
+
+		// and
+		// can access test-server-2 from service in the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectEchoResponse(
+				universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server-2.mesh",
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Instance).To(Equal("test-server-2"))
+		}, "30s", "500ms").Should(Succeed())
+
+		// and
+		// can access test-server from service outside of the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectEchoResponse(
+				universal.Cluster, "mesh-tls-demo-client-no-mesh", testServerContainerName,
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Instance).To(Equal("test-server"))
+		}, "30s", "500ms").Should(Succeed())
+
+		// and
+		// can access test-server-2 from service outside of the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectEchoResponse(
+				universal.Cluster, "mesh-tls-demo-client-no-mesh", testServer2ContainerName,
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Instance).To(Equal("test-server-2"))
+		}, "30s", "500ms").Should(Succeed())
+
+		// when
+		// applied MeshTLS policy to set Strict mode on test-server
+		Expect(universal.Cluster.Install(YamlUniversal(policy))).To(Succeed())
+
+		// then
+		// can access test-server from service in the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectEchoResponse(
+				universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server.mesh",
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Instance).To(Equal("test-server"))
+		}, "30s", "500ms").Should(Succeed())
+
+		// and
+		// can access test-server-2 from service in the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectEchoResponse(
+				universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server-2.mesh",
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Instance).To(Equal("test-server-2"))
+		}, "30s", "500ms").Should(Succeed())
+
+		// and
+		// can access test-server from service outside of the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectFailure(
+				universal.Cluster, "mesh-tls-demo-client-no-mesh", testServerContainerName,
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Exitcode).To(Equal(52))
+		}, "30s", "500ms").Should(Succeed())
+
+		// and
+		// cannot access test-server-2 from service outside of the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectEchoResponse(
+				universal.Cluster, "mesh-tls-demo-client-no-mesh", testServer2ContainerName,
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Instance).To(Equal("test-server-2"))
+		}, "30s", "500ms").Should(Succeed())
+	})
+
+	It("should tls version for 1.3", func() {
+		// given
+		admin, err := universal.Cluster.GetApp(testServerName).GetEnvoyAdminTunnel()
+		Expect(err).ToNot(HaveOccurred())
+
+		policy := fmt.Sprintf(`
+type: MeshTLS
+mesh: %s
+name: mesh-tls-policy
+spec:
+  targetRef:
+    kind: Mesh
+  from:
+    - targetRef:
+        kind: Mesh
+      default:
+        tlsVersion:
+          min: TLS13
+          max: TLS13`, meshName)
+		// when
+		// default strict mode on mesh
+		Expect(universal.Cluster.Install(
+			ResourceUniversal(samples.MeshMTLSBuilder().WithName(meshName).Build()),
+		)).To(Succeed())
+
+		// then
+		// can access test-server from service in the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectEchoResponse(
+				universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server.mesh",
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Instance).To(Equal("test-server"))
+		}, "30s", "1s").Should(Succeed())
+
+		// and
+		// uses tls version 1.2
+		Eventually(func(g Gomega) {
+			s, err := admin.GetStats("listener.(.*)_80.ssl.versions.TLSv1.2")
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(s).To(stats.BeGreaterThanZero())
+		}, "30s", "1s").Should(Succeed())
+
+		// when
+		// applied MeshTLS policy to set 1.3 version on test-server
+		Expect(universal.Cluster.Install(YamlUniversal(policy))).To(Succeed())
+
+		// then
+		// can access test-server from service in the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectEchoResponse(
+				universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server.mesh",
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Instance).To(Equal("test-server"))
+		}, "30s", "1s").MustPassRepeatedly(5).Should(Succeed())
+
+		// and
+		// uses tls version 1.3
+		Eventually(func(g Gomega) {
+			s, err := admin.GetStats("listener.(.*)_80.ssl.versions.TLSv1.3")
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(s).To(stats.BeGreaterThanZero())
+		}, "30s", "1s").Should(Succeed())
+	})
+
+	It("should set cypher and version", func() {
+		// given
+		admin, err := universal.Cluster.GetApp(testServerName).GetEnvoyAdminTunnel()
+		Expect(err).ToNot(HaveOccurred())
+
+		policy := fmt.Sprintf(`
+type: MeshTLS
+mesh: %s
+name: mesh-tls-policy
+spec:
+  targetRef:
+    kind: Mesh
+  from:
+    - targetRef:
+        kind: Mesh
+      default:
+        tlsVersion:
+          min: TLS12
+          max: TLS12
+        tlsCiphers:
+        - "ECDHE-RSA-AES256-GCM-SHA384"`, meshName)
+		// when
+		// default strict mode on mesh
+		Expect(universal.Cluster.Install(
+			ResourceUniversal(samples.MeshMTLSBuilder().WithName(meshName).Build()),
+		)).To(Succeed())
+
+		// then
+		// can access test-server from service in the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectEchoResponse(
+				universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server.mesh",
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Instance).To(Equal("test-server"))
+		}, "30s", "1s").Should(Succeed())
+
+		// and
+		// uses tls version 1.2
+		Eventually(func(g Gomega) {
+			s, err := admin.GetStats("listener.(.*)_80.ssl.versions.TLSv1.2")
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(s).To(stats.BeGreaterThanZero())
+		}, "30s", "1s").Should(Succeed())
+
+		// and
+		// doesn't use specified cypher
+		Eventually(func(g Gomega) {
+			s, err := admin.GetStats("listener.(.*)_80.ssl.ciphers.ECDHE-RSA-AES256-GCM-SHA384")
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(s.Stats).To(BeEmpty())
+		}, "30s", "1s").Should(Succeed())
+
+		// when
+		// applied MeshTLS policy to set cypher on test-server
+		Expect(universal.Cluster.Install(YamlUniversal(policy))).To(Succeed())
+
+		// then
+		// can access test-server from service in the mesh
+		Eventually(func(g Gomega) {
+			responses, err := client.CollectEchoResponse(
+				universal.Cluster, "mesh-tls-demo-client", "mesh-tls-test-server.mesh",
+			)
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(responses.Instance).To(Equal("test-server"))
+		}, "30s", "1s").MustPassRepeatedly(5).Should(Succeed())
+
+		// and
+		// uses tls version 1.2
+		Eventually(func(g Gomega) {
+			s, err := admin.GetStats("listener.(.*)_80.ssl.versions.TLSv1.2")
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(s).To(stats.BeGreaterThanZero())
+		}, "30s", "1s").Should(Succeed())
+
+		// and
+		// doesn't uses specific cypher
+		Eventually(func(g Gomega) {
+			s, err := admin.GetStats("listener.(.*)_80.ssl.ciphers.ECDHE-RSA-AES256-GCM-SHA384")
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(s).To(stats.BeGreaterThanZero())
+		}, "30s", "1s").Should(Succeed())
+	})
+}
diff --git a/test/e2e_env/universal/universal_suite_test.go b/test/e2e_env/universal/universal_suite_test.go
index 92ae5f3f7..bc5f21e69 100644
--- a/test/e2e_env/universal/universal_suite_test.go
+++ b/test/e2e_env/universal/universal_suite_test.go
@@ -26,6 +26,7 @@ import (
 	"github.com/kumahq/kuma/test/e2e_env/universal/meshratelimit"
 	"github.com/kumahq/kuma/test/e2e_env/universal/meshretry"
 	"github.com/kumahq/kuma/test/e2e_env/universal/meshservice"
+	"github.com/kumahq/kuma/test/e2e_env/universal/meshtls"
 	"github.com/kumahq/kuma/test/e2e_env/universal/meshtrafficpermission"
 	"github.com/kumahq/kuma/test/e2e_env/universal/mtls"
 	"github.com/kumahq/kuma/test/e2e_env/universal/observability"
@@ -106,4 +107,5 @@ var (
 	_ = Describe("MeshLoadBalancingStrategy", meshloadbalancingstrategy.Policy, Ordered)
 	_ = Describe("InterCP Server", intercp.InterCP, Ordered)
 	_ = Describe("Prometheus Metrics", observability.PrometheusMetrics, Ordered)
+	_ = Describe("MeshTLS", meshtls.Policy, Ordered)
 )