Repository: istio/istio (go)

Issue:
Ip6tables ISTIO_OUTBOUND chain missing "catch-dport" rules
**Bug description**
When deploying applications into the service mesh in an IPv6 cluster with a `traffic.sidecar.istio.io/includeOutboundPorts` annotation, the respective ip6tables rule to redirect traffic on the specified destination port(s) to Envoy is missing. The corresponding IPv4 iptables rule is created correctly.

```
Environment:
------------
ENVOY_PORT=
INBOUND_CAPTURE_PORT=
ISTIO_INBOUND_INTERCEPTION_MODE=
ISTIO_INBOUND_TPROXY_MARK=
ISTIO_INBOUND_TPROXY_ROUTE_TABLE=
ISTIO_INBOUND_PORTS=
ISTIO_OUTBOUND_PORTS=
ISTIO_LOCAL_EXCLUDE_PORTS=
ISTIO_SERVICE_CIDR=
ISTIO_SERVICE_EXCLUDE_CIDR=

Variables:
----------
PROXY_PORT=15001
PROXY_INBOUND_CAPTURE_PORT=15006
PROXY_TUNNEL_PORT=15008
PROXY_UID=1337
PROXY_GID=1337
INBOUND_INTERCEPTION_MODE=REDIRECT
INBOUND_TPROXY_MARK=1337
INBOUND_TPROXY_ROUTE_TABLE=133
INBOUND_PORTS_INCLUDE=
INBOUND_PORTS_EXCLUDE=15090,15021,15020
OUTBOUND_IP_RANGES_INCLUDE=
OUTBOUND_IP_RANGES_EXCLUDE=
OUTBOUND_PORTS_INCLUDE=10334
OUTBOUND_PORTS_EXCLUDE=
KUBEVIRT_INTERFACES=
ENABLE_INBOUND_IPV6=true

ip -6 addr add ::6/128 dev lo

...

iptables-save
# Generated by iptables-save v1.8.3 on Mon Feb 15 16:12:05 2021
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:ISTIO_INBOUND - [0:0]
:ISTIO_IN_REDIRECT - [0:0]
:ISTIO_OUTPUT - [0:0]
:ISTIO_REDIRECT - [0:0]
-A OUTPUT -p tcp -j ISTIO_OUTPUT
-A ISTIO_INBOUND -p tcp -m tcp --dport 15008 -j RETURN
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006
-A ISTIO_OUTPUT -s 127.0.0.6/32 -o lo -j RETURN
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
-A ISTIO_OUTPUT -p tcp -m tcp --dport 10334 -j ISTIO_REDIRECT  # <<< IPv4 rule
-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
COMMIT
# Completed on Mon Feb 15 16:12:05 2021

---

ip6tables-save
# Generated by ip6tables-save v1.8.3 on Mon Feb 15 16:12:05 2021
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:ISTIO_INBOUND - [0:0]
:ISTIO_IN_REDIRECT - [0:0]
:ISTIO_OUTPUT - [0:0]
:ISTIO_REDIRECT - [0:0]
-A OUTPUT -p tcp -j ISTIO_OUTPUT
-A ISTIO_INBOUND -p tcp -m tcp --dport 15008 -j RETURN
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006
-A ISTIO_OUTPUT -s ::6/128 -o lo -j RETURN
-A ISTIO_OUTPUT ! -d ::1/128 -o lo -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT ! -d ::1/128 -o lo -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -d ::1/128 -j RETURN
# <<< Missing IPv6 rule
-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
COMMIT
# Completed on Mon Feb 15 16:12:05 2021
```

**Affected product area (please put an X in all that apply)**

[ ] Docs
[ ] Installation
[X] Networking
[ ] Performance and Scalability
[ ] Extensions and Telemetry
[ ] Security
[ ] Test and Release
[ ] User Experience
[ ] Developer Infrastructure
[ ] Upgrade

**Affected features (please put an X in all that apply)**

[ ] Multi Cluster
[ ] Virtual Machine
[ ] Multi Control Plane

**Expected behavior**
Ip6tables rule is created analogously to iptables rule

**Steps to reproduce the bug**
Deploy sleep pod with a sidecar container and the `traffic.sidecar.istio.io/includeOutboundPorts=12345` annotation in an IPv6 cluster

**Version** (include the output of `istioctl version --remote` and `kubectl version --short` and `helm version --short` if you used Helm)
1.8.3

**How was Istio installed?**
Helm

**Environment where the bug was observed (cloud vendor, OS, etc)**

Pull request:
[release-1.10] Fixes #30868. Added the missing IPv6 rule.
This is an automated cherry-pick of #33221

Patch:
diff --git a/pilot/pkg/networking/core/v1alpha3/envoyfilter/cluster_patch.go b/pilot/pkg/networking/core/v1alpha3/envoyfilter/cluster_patch.go
index fda8bebad7..86d2f5a162 100644
--- a/pilot/pkg/networking/core/v1alpha3/envoyfilter/cluster_patch.go
+++ b/pilot/pkg/networking/core/v1alpha3/envoyfilter/cluster_patch.go
@@ -59,7 +59,9 @@ func ApplyClusterMerge(pctx networking.EnvoyFilter_PatchContext, efw *model.Envo
 }
 
 // Test if the patch contains a config for TransportSocket
-func mergeTransportSocketCluster(c *cluster.Cluster, cp *model.EnvoyFilterConfigPatchWrapper) (bool, error) {
+// Returns a boolean indicating if the merge was handled by this function; if false, it should still be called
+// outside of this function.
+func mergeTransportSocketCluster(c *cluster.Cluster, cp *model.EnvoyFilterConfigPatchWrapper) (merged bool, err error) {
 	cpValueCast, okCpCast := (cp.Value).(*cluster.Cluster)
 	if !okCpCast {
 		return false, fmt.Errorf("cast of cp.Value failed: %v", okCpCast)
@@ -76,9 +78,19 @@ func mergeTransportSocketCluster(c *cluster.Cluster, cp *model.EnvoyFilterConfig
 				break
 			}
 		}
+		if tsmPatch == nil && len(c.GetTransportSocketMatches()) > 0 {
+			// If we merged we would get both a transport_socket and transport_socket_matches which is not valid
+			// Drop the filter, but indicate that we handled the merge so that the outer function does not try
+			// to merge it again
+			return true, nil
+		}
 	} else if cpValueCast.GetTransportSocket() != nil && c.GetTransportSocket() != nil {
 		if cpValueCast.GetTransportSocket().Name == c.GetTransportSocket().Name {
 			tsmPatch = c.GetTransportSocket()
+		} else {
+			// There is a name mismatch, so we cannot do a deep merge. Instead just replace the transport socket
+			c.TransportSocket = cpValueCast.TransportSocket
+			return true, nil
 		}
 	}
 
diff --git a/pilot/pkg/networking/core/v1alpha3/envoyfilter/listener_patch.go b/pilot/pkg/networking/core/v1alpha3/envoyfilter/listener_patch.go
index 8707d6cebc..0f6b41f02a 100644
--- a/pilot/pkg/networking/core/v1alpha3/envoyfilter/listener_patch.go
+++ b/pilot/pkg/networking/core/v1alpha3/envoyfilter/listener_patch.go
@@ -181,12 +181,12 @@ func doFilterChainOperation(patchContext networking.EnvoyFilter_PatchContext,
 			return
 		} else if cp.Operation == networking.EnvoyFilter_Patch_MERGE {
 
-			ret, err := mergeTransportSocketListener(fc, cp)
+			merged, err := mergeTransportSocketListener(fc, cp)
 			if err != nil {
 				log.Debugf("merge of transport socket failed for listener: %v", err)
 				continue
 			}
-			if !ret {
+			if !merged {
 				proto.Merge(fc, cp.Value)
 			}
 		}
@@ -195,7 +195,9 @@ func doFilterChainOperation(patchContext networking.EnvoyFilter_PatchContext,
 }
 
 // Test if the patch contains a config for TransportSocket
-func mergeTransportSocketListener(fc *xdslistener.FilterChain, cp *model.EnvoyFilterConfigPatchWrapper) (bool, error) {
+// Returns a boolean indicating if the merge was handled by this function; if false, it should still be called
+// outside of this function.
+func mergeTransportSocketListener(fc *xdslistener.FilterChain, cp *model.EnvoyFilterConfigPatchWrapper) (merged bool, err error) {
 	cpValueCast, okCpCast := (cp.Value).(*xdslistener.FilterChain)
 	if !okCpCast {
 		return false, fmt.Errorf("cast of cp.Value failed: %v", okCpCast)
@@ -204,6 +206,10 @@ func mergeTransportSocketListener(fc *xdslistener.FilterChain, cp *model.EnvoyFi
 	// Test if the patch contains a config for TransportSocket
 	applyPatch := false
 	if cpValueCast.GetTransportSocket() != nil {
+		if fc.GetTransportSocket() == nil {
+			// There is no existing filter chain, we will add it outside this function; report back that we did not merge.
+			return false, nil
+		}
 		// Test if the listener contains a config for TransportSocket
 		applyPatch = fc.GetTransportSocket() != nil && cpValueCast.GetTransportSocket().Name == fc.GetTransportSocket().Name
 	} else {
@@ -226,8 +232,8 @@ func mergeTransportSocketListener(fc *xdslistener.FilterChain, cp *model.EnvoyFi
 			proto.Merge(dstListener, retVal)
 		}
 	}
-	// Default: we won't call proto.Merge() if the patch is transportSocket and the listener isn't
-	return true, nil
+	// If we already applied the patch, we skip proto.Merge() in the outer function
+	return applyPatch, nil
 }
 
 func doNetworkFilterListOperation(patchContext networking.EnvoyFilter_PatchContext,
diff --git a/pkg/config/validation/validation.go b/pkg/config/validation/validation.go
index 53c9e18f40..941b5197c9 100644
--- a/pkg/config/validation/validation.go
+++ b/pkg/config/validation/validation.go
@@ -737,6 +737,11 @@ var ValidateEnvoyFilter = registerValidateFunc("ValidateEnvoyFilter",
 					listenerMatch := cp.Match.GetListener()
 					if listenerMatch.FilterChain != nil {
 						if listenerMatch.FilterChain.Filter != nil {
+							if cp.ApplyTo == networking.EnvoyFilter_LISTENER || cp.ApplyTo == networking.EnvoyFilter_FILTER_CHAIN {
+								// This would be an error but is a warning for backwards compatibility
+								errs = appendValidation(errs, WrapWarning(
+									fmt.Errorf("Envoy filter: filter match has no effect when used with %v", cp.ApplyTo))) // nolint: golint,stylecheck
+							}
 							// filter names are required if network filter matches are being made
 							if listenerMatch.FilterChain.Filter.Name == "" {
 								errs = appendValidation(errs, fmt.Errorf("Envoy filter: filter match has no name to match on")) // nolint: golint,stylecheck
diff --git a/releasenotes/notes/30868.yaml b/releasenotes/notes/30868.yaml
new file mode 100644
index 0000000000..30ada81176
--- /dev/null
+++ b/releasenotes/notes/30868.yaml
@@ -0,0 +1,14 @@
+apiVersion: release-notes/v2
+kind: bug-fix
+area: traffic-management
+
+# issue is a list of GitHub issues resolved in this note.
+issue:
+  - https://github.com/istio/istio/issues/30868
+
+docs:
+ - '[reference] https://istio.io/latest/docs/reference/config/annotations/'
+
+releaseNotes:
+- |
+  **Fixed** an issue where IPv6 iptables rules were incorrect when `includeOutboundPorts` annotations were used.
diff --git a/releasenotes/notes/ef-tls-regression.yaml b/releasenotes/notes/ef-tls-regression.yaml
new file mode 100644
index 0000000000..9960d834de
--- /dev/null
+++ b/releasenotes/notes/ef-tls-regression.yaml
@@ -0,0 +1,8 @@
+apiVersion: release-notes/v2
+kind: bug-fix
+area: traffic-management
+issues:
+- 33385
+releaseNotes:
+- |
+  **Fixed** an issue causing Envoy Filters that merge the `transport_socket` field and have a custom transport socket name to be ignored.
diff --git a/tools/istio-iptables/pkg/cmd/run.go b/tools/istio-iptables/pkg/cmd/run.go
index 813db0edc6..6e71074997 100644
--- a/tools/istio-iptables/pkg/cmd/run.go
+++ b/tools/istio-iptables/pkg/cmd/run.go
@@ -298,6 +298,15 @@ func (iptConfigurator *IptablesConfigurator) handleInboundIpv6Rules(ipv6RangesEx
 	for _, cidr := range ipv6RangesExclude.IPNets {
 		iptConfigurator.iptables.AppendRuleV6(constants.ISTIOOUTPUT, constants.NAT, "-d", cidr.String(), "-j", constants.RETURN)
 	}
+
+	// Redirect traffic to Envoy for the included outbound ports
+	if iptConfigurator.cfg.OutboundPortsInclude != "" {
+		for _, port := range split(iptConfigurator.cfg.OutboundPortsInclude) {
+			iptConfigurator.iptables.AppendRuleV6(
+				constants.ISTIOOUTPUT, constants.NAT, "-p", constants.TCP, "--dport", port, "-j", constants.ISTIOREDIRECT)
+		}
+	}
+
 	// Apply outbound IPv6 inclusions.
 	if ipv6RangesInclude.IsWildcard {
 		// Wildcard specified. Redirect all remaining outbound traffic to Envoy.


Test patch:
diff --git a/pilot/pkg/networking/core/v1alpha3/envoyfilter/cluster_patch_test.go b/pilot/pkg/networking/core/v1alpha3/envoyfilter/cluster_patch_test.go
index ea22d5b408..9e5d35524a 100644
--- a/pilot/pkg/networking/core/v1alpha3/envoyfilter/cluster_patch_test.go
+++ b/pilot/pkg/networking/core/v1alpha3/envoyfilter/cluster_patch_test.go
@@ -17,6 +17,7 @@ package envoyfilter
 import (
 	"testing"
 
+	udpa "github.com/cncf/udpa/go/udpa/type/v1"
 	cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
 	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
 	tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
@@ -241,6 +242,11 @@ func TestClusterPatching(t *testing.T) {
 			ApplyTo: networking.EnvoyFilter_CLUSTER,
 			Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
 				Context: networking.EnvoyFilter_SIDECAR_OUTBOUND,
+				ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{
+					Cluster: &networking.EnvoyFilter_ClusterMatch{
+						PortNumber: 443,
+					},
+				},
 			},
 			Patch: &networking.EnvoyFilter_Patch{
 				Operation: networking.EnvoyFilter_Patch_MERGE,
@@ -254,11 +260,33 @@ func TestClusterPatching(t *testing.T) {
 								"tls_minimum_protocol_version":"TLSv1_3"}}}}}`),
 			},
 		},
+		// Patch custom TLS type
+		{
+			ApplyTo: networking.EnvoyFilter_CLUSTER,
+			Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
+				Context: networking.EnvoyFilter_SIDECAR_OUTBOUND,
+				ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{
+					Cluster: &networking.EnvoyFilter_ClusterMatch{
+						PortNumber: 7777,
+					},
+				},
+			},
+			Patch: &networking.EnvoyFilter_Patch{
+				Operation: networking.EnvoyFilter_Patch_MERGE,
+				Value: buildPatchStruct(`
+					{"transport_socket":{
+						"name":"transport_sockets.alts",
+						"typed_config":{
+							"@type":"type.googleapis.com/udpa.type.v1.TypedStruct",
+              "type_url": "type.googleapis.com/envoy.extensions.transport_sockets.alts.v3.Alts",
+							"value":{"handshaker_service":"1.2.3.4"}}}}`),
+			},
+		},
 	}
 
 	sidecarOutboundIn := []*cluster.Cluster{
 		{
-			Name:            "cluster1",
+			Name:            "outbound|443||cluster1",
 			DnsLookupFamily: cluster.Cluster_V4_ONLY,
 			LbPolicy:        cluster.Cluster_ROUND_ROBIN,
 			TransportSocketMatches: []*cluster.Cluster_TransportSocketMatch{
@@ -285,14 +313,14 @@ func TestClusterPatching(t *testing.T) {
 			},
 		},
 		{
-			Name: "cluster2",
+			Name: "outbound|443||cluster2",
 			Http2ProtocolOptions: &core.Http2ProtocolOptions{
 				AllowConnect:  true,
 				AllowMetadata: true,
 			}, LbPolicy: cluster.Cluster_MAGLEV,
 		},
 		{
-			Name:            "cluster3",
+			Name:            "outbound|443||cluster3",
 			DnsLookupFamily: cluster.Cluster_V4_ONLY,
 			LbPolicy:        cluster.Cluster_ROUND_ROBIN,
 			TransportSocket: &core.TransportSocket{
@@ -313,11 +341,63 @@ func TestClusterPatching(t *testing.T) {
 				},
 			},
 		},
+		{
+			Name: "outbound|7777||custom-tls-addition",
+		},
+		{
+			Name:            "outbound|7777||custom-tls-replacement",
+			DnsLookupFamily: cluster.Cluster_V4_ONLY,
+			LbPolicy:        cluster.Cluster_ROUND_ROBIN,
+			TransportSocket: &core.TransportSocket{
+				Name: "envoy.transport_sockets.tls",
+				ConfigType: &core.TransportSocket_TypedConfig{
+					TypedConfig: util.MessageToAny(&tls.UpstreamTlsContext{
+						CommonTlsContext: &tls.CommonTlsContext{
+							TlsParams: &tls.TlsParameters{
+								EcdhCurves:                []string{"X25519"},
+								TlsMinimumProtocolVersion: tls.TlsParameters_TLSv1_1,
+							},
+							TlsCertificateCertificateProviderInstance: &tls.CommonTlsContext_CertificateProviderInstance{
+								InstanceName:    "instance",
+								CertificateName: "certificate",
+							},
+						},
+					}),
+				},
+			},
+		},
+		{
+			Name:            "outbound|7777||custom-tls-replacement-tsm",
+			DnsLookupFamily: cluster.Cluster_V4_ONLY,
+			LbPolicy:        cluster.Cluster_ROUND_ROBIN,
+			TransportSocketMatches: []*cluster.Cluster_TransportSocketMatch{
+				{
+					Name: "tlsMode-istio",
+					TransportSocket: &core.TransportSocket{
+						Name: "envoy.transport_sockets.tls",
+						ConfigType: &core.TransportSocket_TypedConfig{
+							TypedConfig: util.MessageToAny(&tls.UpstreamTlsContext{
+								CommonTlsContext: &tls.CommonTlsContext{
+									TlsParams: &tls.TlsParameters{
+										EcdhCurves:                []string{"X25519"},
+										TlsMinimumProtocolVersion: tls.TlsParameters_TLSv1_1,
+									},
+									TlsCertificateCertificateProviderInstance: &tls.CommonTlsContext_CertificateProviderInstance{
+										InstanceName:    "instance",
+										CertificateName: "certificate",
+									},
+								},
+							}),
+						},
+					},
+				},
+			},
+		},
 	}
 
 	sidecarOutboundOut := []*cluster.Cluster{
 		{
-			Name:            "cluster1",
+			Name:            "outbound|443||cluster1",
 			DnsLookupFamily: cluster.Cluster_V6_ONLY,
 			LbPolicy:        cluster.Cluster_RING_HASH,
 			TransportSocketMatches: []*cluster.Cluster_TransportSocketMatch{
@@ -344,7 +424,7 @@ func TestClusterPatching(t *testing.T) {
 			},
 		},
 		{
-			Name: "cluster2",
+			Name: "outbound|443||cluster2",
 			Http2ProtocolOptions: &core.Http2ProtocolOptions{
 				AllowConnect:  true,
 				AllowMetadata: true,
@@ -365,7 +445,7 @@ func TestClusterPatching(t *testing.T) {
 			},
 		},
 		{
-			Name:            "cluster3",
+			Name:            "outbound|443||cluster3",
 			DnsLookupFamily: cluster.Cluster_V6_ONLY,
 			LbPolicy:        cluster.Cluster_RING_HASH,
 			TransportSocket: &core.TransportSocket{
@@ -387,6 +467,61 @@ func TestClusterPatching(t *testing.T) {
 				},
 			},
 		},
+		{
+			Name:            "outbound|7777||custom-tls-addition",
+			DnsLookupFamily: cluster.Cluster_V6_ONLY,
+			LbPolicy:        cluster.Cluster_RING_HASH,
+			TransportSocket: &core.TransportSocket{
+				Name: "transport_sockets.alts",
+				ConfigType: &core.TransportSocket_TypedConfig{
+					TypedConfig: util.MessageToAny(&udpa.TypedStruct{
+						TypeUrl: "type.googleapis.com/envoy.extensions.transport_sockets.alts.v3.Alts",
+						Value:   buildGolangPatchStruct(`{"handshaker_service":"1.2.3.4"}`),
+					}),
+				},
+			},
+		},
+		{
+			Name:            "outbound|7777||custom-tls-replacement",
+			DnsLookupFamily: cluster.Cluster_V6_ONLY,
+			LbPolicy:        cluster.Cluster_RING_HASH,
+			TransportSocket: &core.TransportSocket{
+				Name: "transport_sockets.alts",
+				ConfigType: &core.TransportSocket_TypedConfig{
+					TypedConfig: util.MessageToAny(&udpa.TypedStruct{
+						TypeUrl: "type.googleapis.com/envoy.extensions.transport_sockets.alts.v3.Alts",
+						Value:   buildGolangPatchStruct(`{"handshaker_service":"1.2.3.4"}`),
+					}),
+				},
+			},
+		},
+		{
+			Name:            "outbound|7777||custom-tls-replacement-tsm",
+			DnsLookupFamily: cluster.Cluster_V6_ONLY,
+			LbPolicy:        cluster.Cluster_RING_HASH,
+			TransportSocketMatches: []*cluster.Cluster_TransportSocketMatch{
+				{
+					Name: "tlsMode-istio",
+					TransportSocket: &core.TransportSocket{
+						Name: "envoy.transport_sockets.tls",
+						ConfigType: &core.TransportSocket_TypedConfig{
+							TypedConfig: util.MessageToAny(&tls.UpstreamTlsContext{
+								CommonTlsContext: &tls.CommonTlsContext{
+									TlsParams: &tls.TlsParameters{
+										EcdhCurves:                []string{"X25519"},
+										TlsMinimumProtocolVersion: tls.TlsParameters_TLSv1_1,
+									},
+									TlsCertificateCertificateProviderInstance: &tls.CommonTlsContext_CertificateProviderInstance{
+										InstanceName:    "instance",
+										CertificateName: "certificate",
+									},
+								},
+							}),
+						},
+					},
+				},
+			},
+		},
 		{Name: "new-cluster1"},
 		{Name: "new-cluster2"},
 	}
diff --git a/pilot/pkg/networking/core/v1alpha3/envoyfilter/listener_patch_test.go b/pilot/pkg/networking/core/v1alpha3/envoyfilter/listener_patch_test.go
index 46bbcc288d..b827f21ab6 100644
--- a/pilot/pkg/networking/core/v1alpha3/envoyfilter/listener_patch_test.go
+++ b/pilot/pkg/networking/core/v1alpha3/envoyfilter/listener_patch_test.go
@@ -21,6 +21,7 @@ import (
 	"strings"
 	"testing"
 
+	udpa "github.com/cncf/udpa/go/udpa/type/v1"
 	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
 	listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
 	fault "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/fault/v3"
@@ -31,6 +32,7 @@ import (
 	"github.com/gogo/protobuf/types"
 	"github.com/golang/protobuf/jsonpb"
 	"github.com/golang/protobuf/proto"
+	structpb "github.com/golang/protobuf/ptypes/struct"
 	"github.com/golang/protobuf/ptypes/wrappers"
 	"github.com/google/go-cmp/cmp"
 	"google.golang.org/protobuf/testing/protocmp"
@@ -82,6 +84,13 @@ func buildPatchStruct(config string) *types.Struct {
 	return val
 }
 
+// nolint: unparam
+func buildGolangPatchStruct(config string) *structpb.Struct {
+	val := &structpb.Struct{}
+	_ = jsonpb.Unmarshal(strings.NewReader(config), val)
+	return val
+}
+
 func newTestEnvironment(serviceDiscovery model.ServiceDiscovery, meshConfig meshconfig.MeshConfig,
 	configStore model.IstioConfigStore) *model.Environment {
 	e := &model.Environment{
@@ -632,11 +641,8 @@ func TestApplyListenerPatches(t *testing.T) {
 				Context: networking.EnvoyFilter_SIDECAR_OUTBOUND,
 				ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
 					Listener: &networking.EnvoyFilter_ListenerMatch{
-						FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
-							Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
-								Name: "envoy.transport_sockets.tls",
-							},
-						},
+						PortNumber:  12345,
+						FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{TransportProtocol: "tls"},
 					},
 				},
 			},
@@ -653,6 +659,50 @@ func TestApplyListenerPatches(t *testing.T) {
 									"tls_minimum_protocol_version":"TLSv1_2"}}}}}`),
 			},
 		},
+		// Patch custom TLS type
+		{
+			ApplyTo: networking.EnvoyFilter_FILTER_CHAIN,
+			Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
+				Context: networking.EnvoyFilter_SIDECAR_OUTBOUND,
+				ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
+					Listener: &networking.EnvoyFilter_ListenerMatch{
+						PortNumber: 7777,
+					},
+				},
+			},
+			Patch: &networking.EnvoyFilter_Patch{
+				Operation: networking.EnvoyFilter_Patch_MERGE,
+				Value: buildPatchStruct(`
+					{"transport_socket":{
+						"name":"transport_sockets.alts",
+						"typed_config":{
+							"@type":"type.googleapis.com/udpa.type.v1.TypedStruct",
+              "type_url": "type.googleapis.com/envoy.extensions.transport_sockets.alts.v3.Alts",
+							"value":{"handshaker_service":"1.2.3.4"}}}}`),
+			},
+		},
+		// Patch custom TLS type to a FC without TLS already set
+		{
+			ApplyTo: networking.EnvoyFilter_FILTER_CHAIN,
+			Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
+				Context: networking.EnvoyFilter_SIDECAR_OUTBOUND,
+				ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
+					Listener: &networking.EnvoyFilter_ListenerMatch{
+						PortNumber: 7778,
+					},
+				},
+			},
+			Patch: &networking.EnvoyFilter_Patch{
+				Operation: networking.EnvoyFilter_Patch_MERGE,
+				Value: buildPatchStruct(`
+					{"transport_socket":{
+						"name":"transport_sockets.alts",
+						"typed_config":{
+							"@type":"type.googleapis.com/udpa.type.v1.TypedStruct",
+              "type_url": "type.googleapis.com/envoy.extensions.transport_sockets.alts.v3.Alts",
+							"value":{"handshaker_service":"1.2.3.4"}}}}`),
+			},
+		},
 		{
 			ApplyTo: networking.EnvoyFilter_NETWORK_FILTER,
 			Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
@@ -829,6 +879,50 @@ func TestApplyListenerPatches(t *testing.T) {
 				},
 			},
 		},
+		{
+			Name: "custom-tls-replacement",
+			Address: &core.Address{
+				Address: &core.Address_SocketAddress{
+					SocketAddress: &core.SocketAddress{
+						PortSpecifier: &core.SocketAddress_PortValue{
+							PortValue: 7777,
+						},
+					},
+				},
+			},
+			FilterChains: []*listener.FilterChain{
+				{
+					TransportSocket: &core.TransportSocket{
+						Name: "envoy.transport_sockets.tls",
+						ConfigType: &core.TransportSocket_TypedConfig{
+							TypedConfig: util.MessageToAny(&tls.DownstreamTlsContext{
+								CommonTlsContext: &tls.CommonTlsContext{
+									TlsParams: &tls.TlsParameters{},
+								},
+							}),
+						},
+					},
+					Filters: []*listener.Filter{{Name: "filter"}},
+				},
+			},
+		},
+		{
+			Name: "custom-tls-addition",
+			Address: &core.Address{
+				Address: &core.Address_SocketAddress{
+					SocketAddress: &core.SocketAddress{
+						PortSpecifier: &core.SocketAddress_PortValue{
+							PortValue: 7778,
+						},
+					},
+				},
+			},
+			FilterChains: []*listener.FilterChain{
+				{
+					Filters: []*listener.Filter{{Name: "filter"}},
+				},
+			},
+		},
 	}
 
 	sidecarOutboundOut := []*listener.Listener{
@@ -998,6 +1092,58 @@ func TestApplyListenerPatches(t *testing.T) {
 				},
 			},
 		},
+		{
+			Name: "custom-tls-replacement",
+			Address: &core.Address{
+				Address: &core.Address_SocketAddress{
+					SocketAddress: &core.SocketAddress{
+						PortSpecifier: &core.SocketAddress_PortValue{
+							PortValue: 7777,
+						},
+					},
+				},
+			},
+			FilterChains: []*listener.FilterChain{
+				{
+					TransportSocket: &core.TransportSocket{
+						Name: "transport_sockets.alts",
+						ConfigType: &core.TransportSocket_TypedConfig{
+							TypedConfig: util.MessageToAny(&udpa.TypedStruct{
+								TypeUrl: "type.googleapis.com/envoy.extensions.transport_sockets.alts.v3.Alts",
+								Value:   buildGolangPatchStruct(`{"handshaker_service":"1.2.3.4"}`),
+							}),
+						},
+					},
+					Filters: []*listener.Filter{{Name: "filter"}},
+				},
+			},
+		},
+		{
+			Name: "custom-tls-addition",
+			Address: &core.Address{
+				Address: &core.Address_SocketAddress{
+					SocketAddress: &core.SocketAddress{
+						PortSpecifier: &core.SocketAddress_PortValue{
+							PortValue: 7778,
+						},
+					},
+				},
+			},
+			FilterChains: []*listener.FilterChain{
+				{
+					TransportSocket: &core.TransportSocket{
+						Name: "transport_sockets.alts",
+						ConfigType: &core.TransportSocket_TypedConfig{
+							TypedConfig: util.MessageToAny(&udpa.TypedStruct{
+								TypeUrl: "type.googleapis.com/envoy.extensions.transport_sockets.alts.v3.Alts",
+								Value:   buildGolangPatchStruct(`{"handshaker_service":"1.2.3.4"}`),
+							}),
+						},
+					},
+					Filters: []*listener.Filter{{Name: "filter"}},
+				},
+			},
+		},
 		{
 			Name: "new-outbound-listener1",
 		},
diff --git a/pkg/config/validation/validation_test.go b/pkg/config/validation/validation_test.go
index 6a2bfd2aee..9488de3ed9 100644
--- a/pkg/config/validation/validation_test.go
+++ b/pkg/config/validation/validation_test.go
@@ -3447,7 +3447,7 @@ func TestValidateEnvoyFilter(t *testing.T) {
 		{name: "listener with invalid filter match", in: &networking.EnvoyFilter{
 			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
 				{
-					ApplyTo: networking.EnvoyFilter_LISTENER,
+					ApplyTo: networking.EnvoyFilter_NETWORK_FILTER,
 					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
 						ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
 							Listener: &networking.EnvoyFilter_ListenerMatch{
@@ -3467,7 +3467,7 @@ func TestValidateEnvoyFilter(t *testing.T) {
 		{name: "listener with sub filter match and invalid applyTo", in: &networking.EnvoyFilter{
 			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
 				{
-					ApplyTo: networking.EnvoyFilter_LISTENER,
+					ApplyTo: networking.EnvoyFilter_NETWORK_FILTER,
 					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
 						ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
 							Listener: &networking.EnvoyFilter_ListenerMatch{
diff --git a/security/pkg/pki/util/generate_cert_test.go b/security/pkg/pki/util/generate_cert_test.go
index f612066d75..d136d7de72 100644
--- a/security/pkg/pki/util/generate_cert_test.go
+++ b/security/pkg/pki/util/generate_cert_test.go
@@ -75,7 +75,7 @@ func TestGenCertKeyFromOptions(t *testing.T) {
 
 	rsaCaCertPem, rsaCaPrivPem, err := GenCertKeyFromOptions(rsaCaCertOptions)
 	if err != nil {
-		t.Error(err)
+		t.Fatal(err)
 	}
 
 	// Options to generate a CA cert with EC.
@@ -95,7 +95,7 @@ func TestGenCertKeyFromOptions(t *testing.T) {
 
 	ecCaCertPem, ecCaPrivPem, err := GenCertKeyFromOptions(ecCaCertOptions)
 	if err != nil {
-		t.Error(err)
+		t.Fatal(err)
 	}
 
 	fields := &VerifyFields{
@@ -108,31 +108,31 @@ func TestGenCertKeyFromOptions(t *testing.T) {
 		Host:        host,
 	}
 	if VerifyCertificate(rsaCaPrivPem, rsaCaCertPem, rsaCaCertPem, fields) != nil {
-		t.Error(err)
+		t.Fatal(err)
 	}
 
 	if VerifyCertificate(ecCaPrivPem, ecCaCertPem, ecCaCertPem, fields) != nil {
-		t.Error(err)
+		t.Fatal(err)
 	}
 
 	rsaCaCert, err := ParsePemEncodedCertificate(rsaCaCertPem)
 	if err != nil {
-		t.Error(err)
+		t.Fatal(err)
 	}
 
 	ecCaCert, err := ParsePemEncodedCertificate(ecCaCertPem)
 	if err != nil {
-		t.Error(err)
+		t.Fatal(err)
 	}
 
 	rsaCaPriv, err := ParsePemEncodedKey(rsaCaPrivPem)
 	if err != nil {
-		t.Error(err)
+		t.Fatal(err)
 	}
 
-	ecCaPriv, err := ParsePemEncodedKey(rsaCaPrivPem)
+	ecCaPriv, err := ParsePemEncodedKey(ecCaPrivPem)
 	if err != nil {
-		t.Error(err)
+		t.Fatal(err)
 	}
 
 	notBefore := now.Add(-5 * time.Minute)
@@ -560,7 +560,11 @@ func TestGenCertKeyFromOptions(t *testing.T) {
 
 			for _, host := range strings.Split(certOptions.Host, ",") {
 				c.verifyFields.Host = host
-				if err := VerifyCertificate(privPem, certPem, rsaCaCertPem, c.verifyFields); err != nil {
+				root := rsaCaCertPem
+				if c.certOptions.ECSigAlg != "" {
+					root = ecCaCertPem
+				}
+				if err := VerifyCertificate(privPem, certPem, root, c.verifyFields); err != nil {
 					t.Errorf("[%s] cert verification error: %v", id, err)
 				}
 			}
diff --git a/tools/istio-iptables/pkg/cmd/run_test.go b/tools/istio-iptables/pkg/cmd/run_test.go
index 894d093d4c..33ab9a0846 100644
--- a/tools/istio-iptables/pkg/cmd/run_test.go
+++ b/tools/istio-iptables/pkg/cmd/run_test.go
@@ -404,6 +404,44 @@ func TestHandleInboundIpv6RulesWithUidGid(t *testing.T) {
 	}
 }
 
+func TestHandleInboundIpv6RulesWithOutboundPorts(t *testing.T) {
+	cfg := constructTestConfig()
+	cfg.InboundPortsInclude = ""
+	cfg.OutboundPortsInclude = "32000,31000"
+	cfg.EnableInboundIPv6 = true
+
+	iptConfigurator := NewIptablesConfigurator(cfg, &dep.StdoutStubDependencies{})
+	ipv6Range := NetworkRange{
+		IsWildcard: false,
+		IPNets:     nil,
+	}
+	iptConfigurator.handleInboundIpv6Rules(ipv6Range, ipv6Range)
+	actual := FormatIptablesCommands(iptConfigurator.iptables.BuildV6())
+	expected := []string{
+		"ip6tables -t nat -N ISTIO_INBOUND",
+		"ip6tables -t nat -N ISTIO_REDIRECT",
+		"ip6tables -t nat -N ISTIO_IN_REDIRECT",
+		"ip6tables -t nat -N ISTIO_OUTPUT",
+		"ip6tables -t nat -A ISTIO_INBOUND -p tcp --dport 15008 -j RETURN",
+		"ip6tables -t nat -A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001",
+		"ip6tables -t nat -A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006",
+		"ip6tables -t nat -A OUTPUT -p tcp -j ISTIO_OUTPUT",
+		"ip6tables -t nat -A ISTIO_OUTPUT -o lo -s ::6/128 -j RETURN",
+		"ip6tables -t nat -A ISTIO_OUTPUT -o lo ! -d ::1/128 -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT",
+		"ip6tables -t nat -A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN",
+		"ip6tables -t nat -A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN",
+		"ip6tables -t nat -A ISTIO_OUTPUT -o lo ! -d ::1/128 -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT",
+		"ip6tables -t nat -A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN",
+		"ip6tables -t nat -A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN",
+		"ip6tables -t nat -A ISTIO_OUTPUT -d ::1/128 -j RETURN",
+		"ip6tables -t nat -A ISTIO_OUTPUT -p tcp --dport 32000 -j ISTIO_REDIRECT",
+		"ip6tables -t nat -A ISTIO_OUTPUT -p tcp --dport 31000 -j ISTIO_REDIRECT",
+	}
+	if !reflect.DeepEqual(actual, expected) {
+		t.Errorf("Output mismatch.\nExpected: %#v\nActual: %#v", expected, actual)
+	}
+}
+
 func TestHandleInboundIpv4RulesWithWildCard(t *testing.T) {
 	cfg := constructTestConfig()