Repository: istio/istio (go)

Issue:
Validating webhook installation regression
We introduced per-revision validating webhook configurations in [https://github.com/istio/istio/pull/33044/files](https://github.com/istio/istio/pull/33044). As part of this, we had to keep the old validating webhook around to keep from purging it on revisioned upgrade with Helm. However, having a lame-duck validating webhook configuration around, even if it never moves to `FailClose`, has a cost:

1. Produces error logs about missing CA bundle on each resource application
2. Webhook still gets called on each resource application

In addition, with Helm-based install, we never install the revisioned webhook that is meant to actually handle validation since `global: configValidation` is not set in the `istio-discovery` chart.

Pull request:
[release-1.11] Remove old validating webhook configuration from base
Fixes #34452 

This resource was kept around to keep from purging the old validating webhook configuration on revisioned upgrade with Helm. However, having a lame-duck validating webhook configuration around, even if it never moves to `FailClose`, has a cost:

1. Produces error logs about missing CA bundle on each resource application
2. Webhook still gets called on each resource application

Given these drawbacks it makes sense to remove the old validator to prevent a regression. Should have a corresponding warning in Helm revisioned upgrade doc that  for 1.10->1.11 the old validator will be purged on new base installation.

[ ] Configuration Infrastructure
[ ] Docs
[x] Installation
[ ] Networking
[ ] Performance and Scalability
[ ] Policies and Telemetry
[ ] Security
[ ] Test and Release
[ ] User Experience
[ ] Developer Infrastructure


Pull Request Attributes

Please check any characteristics that apply to this pull request. 

[x] Does not have any [user-facing](https://github.com/istio/istio/tree/master/releasenotes#when-to-add-release-notes) changes. This may include CLI changes, API changes, behavior changes, performance improvements, etc.


Patch:
diff --git a/manifests/addons/gen.sh b/manifests/addons/gen.sh
index eaee558223..030d318c1f 100755
--- a/manifests/addons/gen.sh
+++ b/manifests/addons/gen.sh
@@ -30,7 +30,7 @@ TMP=$(mktemp -d)
 {
 helm3 template kiali-server \
   --namespace istio-system \
-  --version 1.36.0 \
+  --version 1.38.0 \
   --include-crds \
   --set nameOverride=kiali \
   --set fullnameOverride=kiali \
diff --git a/manifests/addons/values-kiali.yaml b/manifests/addons/values-kiali.yaml
index d4b50d4e33..76399cc9af 100644
--- a/manifests/addons/values-kiali.yaml
+++ b/manifests/addons/values-kiali.yaml
@@ -6,7 +6,7 @@ deployment:
     sidecar.istio.io/inject: "false"
   accessible_namespaces:
   - '**'
-  image_version: v1.36
+  image_version: v1.38
   ingress_enabled: false
 login_token:
   signing_key: CHANGEME
diff --git a/manifests/charts/base/files/gen-istio-cluster.yaml b/manifests/charts/base/files/gen-istio-cluster.yaml
index 35b8526a5a..9fd002fc5a 100644
--- a/manifests/charts/base/files/gen-istio-cluster.yaml
+++ b/manifests/charts/base/files/gen-istio-cluster.yaml
@@ -6032,37 +6032,3 @@ subjects:
   - kind: ServiceAccount
     name: istiod-service-account
     namespace: istio-system
----
-# Source: base/templates/validatingwebhookconfiguration.yaml
-apiVersion: admissionregistration.k8s.io/v1
-kind: ValidatingWebhookConfiguration
-metadata:
-  name: istiod-istio-system
-  labels:
-    app: istiod
-    release: istio
-    istio: istiod
-webhooks:
-  - name: validation.istio.io
-    clientConfig:
-      service:
-        name: istiod
-        namespace: istio-system
-        path: "/validate"
-    rules:
-      - operations:
-        - CREATE
-        - UPDATE
-        apiGroups:
-        - security.istio.io
-        - networking.istio.io
-        apiVersions:
-        - "*"
-        resources:
-        - "*"
-    # Fail open until the validation webhook is ready. The webhook controller
-    # will update this to `Fail` and patch in the `caBundle` when the webhook
-    # endpoint is ready.
-    failurePolicy: Ignore
-    sideEffects: None
-    admissionReviewVersions: ["v1beta1", "v1"]
diff --git a/manifests/charts/base/templates/validatingwebhookconfiguration.yaml b/manifests/charts/base/templates/validatingwebhookconfiguration.yaml
deleted file mode 100644
index 62be73a588..0000000000
--- a/manifests/charts/base/templates/validatingwebhookconfiguration.yaml
+++ /dev/null
@@ -1,39 +0,0 @@
-{{- if .Values.global.configValidation }}
-apiVersion: admissionregistration.k8s.io/v1
-kind: ValidatingWebhookConfiguration
-metadata:
-  name: istiod-{{ .Values.global.istioNamespace }}
-  labels:
-    app: istiod
-    release: {{ .Release.Name }}
-    istio: istiod
-webhooks:
-  - name: validation.istio.io
-    clientConfig:
-      {{- if .Values.base.validationURL }}
-      url: {{ .Values.base.validationURL }}
-      {{- else }}
-      service:
-        name: istiod
-        namespace: {{ .Values.global.istioNamespace }}
-        path: "/validate"
-      {{- end }}
-    rules:
-      - operations:
-        - CREATE
-        - UPDATE
-        apiGroups:
-        - security.istio.io
-        - networking.istio.io
-        apiVersions:
-        - "*"
-        resources:
-        - "*"
-    # Fail open until the validation webhook is ready. The webhook controller
-    # will update this to `Fail` and patch in the `caBundle` when the webhook
-    # endpoint is ready.
-    failurePolicy: Ignore
-    sideEffects: None
-    admissionReviewVersions: ["v1beta1", "v1"]
----
-{{- end }}
diff --git a/manifests/charts/istio-control/istio-discovery/files/gen-istio.yaml b/manifests/charts/istio-control/istio-discovery/files/gen-istio.yaml
index 99d555712d..5f374363c1 100644
--- a/manifests/charts/istio-control/istio-discovery/files/gen-istio.yaml
+++ b/manifests/charts/istio-control/istio-discovery/files/gen-istio.yaml
@@ -74,6 +74,7 @@ data:
       "global": {
         "caAddress": "",
         "configCluster": false,
+        "configValidation": true,
         "defaultPodDisruptionBudget": {
           "enabled": true
         },
@@ -2944,3 +2945,76 @@ webhooks:
       - "true"
     - key: istio.io/rev
       operator: DoesNotExist
+---
+# Source: istio-discovery/templates/validatingwebhookconfiguration.yaml
+apiVersion: admissionregistration.k8s.io/v1
+kind: ValidatingWebhookConfiguration
+metadata:
+  name: istio-validator-istio-system
+  labels:
+    app: istiod
+    release: istio
+    istio: istiod
+    istio.io/rev: default
+webhooks:
+  # Webhook handling per-revision validation. Mostly here so we can determine whether webhooks
+  # are rejecting invalid configs on a per-revision basis.
+  - name: rev.validation.istio.io
+    clientConfig:
+      # Should change from base but cannot for API compat
+      service:
+        name: istiod
+        namespace: istio-system
+        path: "/validate"
+      caBundle: "" # patched at runtime when the webhook is ready.
+    rules:
+      - operations:
+          - CREATE
+          - UPDATE
+        apiGroups:
+          - security.istio.io
+          - networking.istio.io
+        apiVersions:
+          - "*"
+        resources:
+          - "*"
+    # Fail open until the validation webhook is ready. The webhook controller
+    # will update this to `Fail` and patch in the `caBundle` when the webhook
+    # endpoint is ready.
+    failurePolicy: Ignore
+    sideEffects: None
+    admissionReviewVersions: ["v1beta1", "v1"]
+    objectSelector:
+      matchExpressions:
+        - key: istio.io/rev
+          operator: In
+          values:
+          - "default"
+  # Webhook handling default validation
+  - name: validation.istio.io
+    clientConfig:
+      # Should change from base but cannot for API compat
+      service:
+        name: istiod
+        namespace: istio-system
+        path: "/validate"
+      caBundle: ""
+    rules:
+      - operations:
+          - CREATE
+          - UPDATE
+        apiGroups:
+          - security.istio.io
+          - networking.istio.io
+          - telemetry.istio.io
+        apiVersions:
+          - "*"
+        resources:
+          - "*"
+    failurePolicy: Ignore
+    sideEffects: None
+    admissionReviewVersions: ["v1beta1", "v1"]
+    objectSelector:
+      matchExpressions:
+        - key: istio.io/rev
+          operator: DoesNotExist
diff --git a/manifests/charts/istio-control/istio-discovery/values.yaml b/manifests/charts/istio-control/istio-discovery/values.yaml
index e759f09223..85fc3ebe72 100644
--- a/manifests/charts/istio-control/istio-discovery/values.yaml
+++ b/manifests/charts/istio-control/istio-discovery/values.yaml
@@ -112,8 +112,8 @@ sidecarInjectorWebhook:
   templates: {}
 
   # Default templates specifies a set of default templates that are used in sidecar injection.
-  # By default, a template `sidecar` is always provided, which contains the template of default sidecar. 
-  # To inject other additional templates, define it using the `templates` option, and add it to 
+  # By default, a template `sidecar` is always provided, which contains the template of default sidecar.
+  # To inject other additional templates, define it using the `templates` option, and add it to
   # the default templates list.
   # For example:
   #
@@ -517,6 +517,9 @@ global:
   # Use the Mesh Control Protocol (MCP) for configuring Istiod. Requires an MCP source.
   useMCP: false
 
+  # Determines whether this istiod performs resource validation.
+  configValidation: true
+
 base:
   # For istioctl usage to disable istio config crds in base
   enableIstioConfigCRDs: true
diff --git a/manifests/charts/istiod-remote/values.yaml b/manifests/charts/istiod-remote/values.yaml
index 942d7d1964..bca741cda0 100644
--- a/manifests/charts/istiod-remote/values.yaml
+++ b/manifests/charts/istiod-remote/values.yaml
@@ -90,8 +90,8 @@ sidecarInjectorWebhook:
   # This is intended for advanced configuration only; most users should use the built in template
   templates: {}
   # Default templates specifies a set of default templates that are used in sidecar injection.
-  # By default, a template `sidecar` is always provided, which contains the template of default sidecar. 
-  # To inject other additional templates, define it using the `templates` option, and add it to 
+  # By default, a template `sidecar` is always provided, which contains the template of default sidecar.
+  # To inject other additional templates, define it using the `templates` option, and add it to
   # the default templates list.
   # For example:
   #
@@ -444,6 +444,8 @@ global:
       address: ""
   # Use the Mesh Control Protocol (MCP) for configuring Istiod. Requires an MCP source.
   useMCP: false
+  # Determines whether this istiod performs resource validation.
+  configValidation: true
 base:
   # For istioctl usage to disable istio config crds in base
   enableIstioConfigCRDs: true
diff --git a/pkg/dns/server/name_table.go b/pkg/dns/server/name_table.go
index 9419ced442..8983235685 100644
--- a/pkg/dns/server/name_table.go
+++ b/pkg/dns/server/name_table.go
@@ -55,9 +55,17 @@ func BuildNameTable(cfg Config) *dnsProto.NameTable {
 		svcAddress := svc.GetServiceAddressForProxy(cfg.Node)
 		var addressList []string
 
-		// The IP will be unspecified here if its headless service or if the auto
-		// IP allocation logic for service entry was unable to allocate an IP.
-		if svcAddress == constants.UnspecifiedIP {
+		if svcAddress != constants.UnspecifiedIP {
+			// Filter out things we cannot parse as IP. Generally this means CIDRs, as anything else
+			// should be caught in validation.
+			if addr := net.ParseIP(svcAddress); addr == nil {
+				continue
+			}
+			addressList = append(addressList, svcAddress)
+		} else {
+			// The IP will be unspecified here if its headless service or if the auto
+			// IP allocation logic for service entry was unable to allocate an IP.
+
 			// For all k8s headless services, populate the dns table with the endpoint IPs as k8s does.
 			// And for each individual pod, populate the dns table with the endpoint IP with a manufactured host name.
 			if svc.Attributes.ServiceRegistry == provider.Kubernetes &&
@@ -106,20 +114,23 @@ func BuildNameTable(cfg Config) *dnsProto.NameTable {
 					// TODO: should we skip the node's own IP like we do in listener?
 					addressList = append(addressList, instance.Endpoint.Address)
 				}
+			} else if svc.Attributes.ServiceRegistry == provider.External &&
+				svc.Resolution == model.Passthrough && len(svc.Ports) > 0 {
+				for _, instance := range cfg.Push.ServiceInstancesByPort(svc, svc.Ports[0].Port, nil) {
+					sameNetwork := cfg.Node.InNetwork(instance.Endpoint.Network)
+					sameCluster := cfg.Node.InCluster(instance.Endpoint.Locality.ClusterID)
+					if sameNetwork {
+						if sameCluster || cfg.MulticlusterHeadlessEnabled {
+							addressList = append(addressList, instance.Endpoint.Address)
+						}
+					}
+				}
 			}
-
 			if len(addressList) == 0 {
 				// could not reliably determine the addresses of endpoints of headless service
 				// or this is not a k8s service
 				continue
 			}
-		} else {
-			// Filter out things we cannot parse as IP. Generally this means CIDRs, as anything else
-			// should be caught in validation.
-			if addr := net.ParseIP(svcAddress); addr == nil {
-				continue
-			}
-			addressList = append(addressList, svcAddress)
 		}
 
 		nameInfo := &dnsProto.NameTable_NameInfo{
diff --git a/samples/addons/kiali.yaml b/samples/addons/kiali.yaml
index b11f7805a4..4e5986e7e6 100644
--- a/samples/addons/kiali.yaml
+++ b/samples/addons/kiali.yaml
@@ -6,12 +6,12 @@ metadata:
   name: kiali
   namespace: istio-system
   labels:
-    helm.sh/chart: kiali-server-1.36.0
+    helm.sh/chart: kiali-server-1.38.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.36.0"
-    app.kubernetes.io/version: "v1.36.0"
+    version: "v1.38.0"
+    app.kubernetes.io/version: "v1.38.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
 ...
@@ -23,12 +23,12 @@ metadata:
   name: kiali
   namespace: istio-system
   labels:
-    helm.sh/chart: kiali-server-1.36.0
+    helm.sh/chart: kiali-server-1.38.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.36.0"
-    app.kubernetes.io/version: "v1.36.0"
+    version: "v1.38.0"
+    app.kubernetes.io/version: "v1.38.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
 data:
@@ -52,7 +52,7 @@ data:
       image_name: quay.io/kiali/kiali
       image_pull_policy: Always
       image_pull_secrets: []
-      image_version: v1.36
+      image_version: v1.38
       ingress_enabled: false
       instance_name: kiali
       logger:
@@ -74,7 +74,7 @@ data:
       service_annotations: {}
       service_type: ""
       tolerations: []
-      version_label: v1.36.0
+      version_label: v1.38.0
       view_only_mode: false
     external_services:
       custom_dashboards:
@@ -98,12 +98,12 @@ kind: ClusterRole
 metadata:
   name: kiali-viewer
   labels:
-    helm.sh/chart: kiali-server-1.36.0
+    helm.sh/chart: kiali-server-1.38.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.36.0"
-    app.kubernetes.io/version: "v1.36.0"
+    version: "v1.38.0"
+    app.kubernetes.io/version: "v1.38.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
 rules:
@@ -112,7 +112,6 @@ rules:
   - configmaps
   - endpoints
   - pods/log
-  - pods/proxy
   verbs:
   - get
   - list
@@ -196,12 +195,12 @@ kind: ClusterRole
 metadata:
   name: kiali
   labels:
-    helm.sh/chart: kiali-server-1.36.0
+    helm.sh/chart: kiali-server-1.38.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.36.0"
-    app.kubernetes.io/version: "v1.36.0"
+    version: "v1.38.0"
+    app.kubernetes.io/version: "v1.38.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
 rules:
@@ -210,7 +209,6 @@ rules:
   - configmaps
   - endpoints
   - pods/log
-  - pods/proxy
   verbs:
   - get
   - list
@@ -304,12 +302,12 @@ kind: ClusterRoleBinding
 metadata:
   name: kiali
   labels:
-    helm.sh/chart: kiali-server-1.36.0
+    helm.sh/chart: kiali-server-1.38.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.36.0"
-    app.kubernetes.io/version: "v1.36.0"
+    version: "v1.38.0"
+    app.kubernetes.io/version: "v1.38.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
 roleRef:
@@ -329,12 +327,12 @@ metadata:
   name: kiali-controlplane
   namespace: istio-system
   labels:
-    helm.sh/chart: kiali-server-1.36.0
+    helm.sh/chart: kiali-server-1.38.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.36.0"
-    app.kubernetes.io/version: "v1.36.0"
+    version: "v1.38.0"
+    app.kubernetes.io/version: "v1.38.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
 rules:
@@ -352,12 +350,12 @@ metadata:
   name: kiali-controlplane
   namespace: istio-system
   labels:
-    helm.sh/chart: kiali-server-1.36.0
+    helm.sh/chart: kiali-server-1.38.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.36.0"
-    app.kubernetes.io/version: "v1.36.0"
+    version: "v1.38.0"
+    app.kubernetes.io/version: "v1.38.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
 roleRef:
@@ -377,12 +375,12 @@ metadata:
   name: kiali
   namespace: istio-system
   labels:
-    helm.sh/chart: kiali-server-1.36.0
+    helm.sh/chart: kiali-server-1.38.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.36.0"
-    app.kubernetes.io/version: "v1.36.0"
+    version: "v1.38.0"
+    app.kubernetes.io/version: "v1.38.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
   annotations:
@@ -406,12 +404,12 @@ metadata:
   name: kiali
   namespace: istio-system
   labels:
-    helm.sh/chart: kiali-server-1.36.0
+    helm.sh/chart: kiali-server-1.38.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.36.0"
-    app.kubernetes.io/version: "v1.36.0"
+    version: "v1.38.0"
+    app.kubernetes.io/version: "v1.38.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
 spec:
@@ -429,12 +427,12 @@ spec:
     metadata:
       name: kiali
       labels:
-        helm.sh/chart: kiali-server-1.36.0
+        helm.sh/chart: kiali-server-1.38.0
         app: kiali
         app.kubernetes.io/name: kiali
         app.kubernetes.io/instance: kiali
-        version: "v1.36.0"
-        app.kubernetes.io/version: "v1.36.0"
+        version: "v1.38.0"
+        app.kubernetes.io/version: "v1.38.0"
         app.kubernetes.io/managed-by: Helm
         app.kubernetes.io/part-of: "kiali"
       annotations:
@@ -445,7 +443,7 @@ spec:
     spec:
       serviceAccountName: kiali
       containers:
-      - image: "quay.io/kiali/kiali:v1.36"
+      - image: "quay.io/kiali/kiali:v1.38"
         imagePullPolicy: Always
         name: kiali
         command:
@@ -496,6 +494,8 @@ spec:
           mountPath: "/kiali-cert"
         - name: kiali-secret
           mountPath: "/kiali-secret"
+        - name: kiali-cabundle
+          mountPath: "/kiali-cabundle"
       volumes:
       - name: kiali-configuration
         configMap:
@@ -508,4 +508,8 @@ spec:
         secret:
           secretName: kiali
           optional: true
+      - name: kiali-cabundle
+        configMap:
+          name: kiali-cabundle
+          optional: true
 ...


Test patch:
diff --git a/operator/cmd/mesh/manifest-generate_test.go b/operator/cmd/mesh/manifest-generate_test.go
index afa3f7ff66..599d13b037 100644
--- a/operator/cmd/mesh/manifest-generate_test.go
+++ b/operator/cmd/mesh/manifest-generate_test.go
@@ -225,9 +225,6 @@ func TestManifestGenerateIstiodRemote(t *testing.T) {
 		mwc := mustGetMutatingWebhookConfiguration(g, objs, "istio-sidecar-injector").Unstructured()
 		g.Expect(mwc).Should(HavePathValueEqual(PathValue{"webhooks.[0].clientConfig.url", "https://xxx:15017/inject"}))
 
-		vwc := mustGetValidatingWebhookConfiguration(g, objs, "istiod-istio-system").Unstructured()
-		g.Expect(vwc).Should(HavePathValueEqual(PathValue{"webhooks.[0].clientConfig.url", "https://xxx:15017/validate"}))
-
 		ep := mustGetEndpoint(g, objs, "istiod").Unstructured()
 		g.Expect(ep).Should(HavePathValueEqual(PathValue{"subsets.[0].addresses.[0]", endpointSubsetAddressVal("", "169.10.112.88", "")}))
 		g.Expect(ep).Should(HavePathValueContain(PathValue{"subsets.[0].ports.[0]", portVal("tcp-istiod", 15012, -1)}))
diff --git a/operator/cmd/mesh/test-util.go b/operator/cmd/mesh/test-util.go
index 7b9330a0b6..9f73c10c8c 100644
--- a/operator/cmd/mesh/test-util.go
+++ b/operator/cmd/mesh/test-util.go
@@ -224,13 +224,6 @@ func mustGetMutatingWebhookConfiguration(g *gomega.WithT, objs *ObjectSet, mutat
 	return obj
 }
 
-// mustGetValidatingWebhookConfiguration returns the validatingWebhookConfiguration with the given name or fails if it's not found in objs.
-func mustGetValidatingWebhookConfiguration(g *gomega.WithT, objs *ObjectSet, validatingWebhookConfigurationName string) *object.K8sObject {
-	obj := objs.kind(name2.ValidatingWebhookConfigurationStr).nameEquals(validatingWebhookConfigurationName)
-	g.Expect(obj).Should(gomega.Not(gomega.BeNil()))
-	return obj
-}
-
 // HavePathValueEqual matches map[string]interface{} tree against a PathValue.
 func HavePathValueEqual(expected interface{}) types.GomegaMatcher {
 	return &HavePathValueEqualMatcher{
diff --git a/pkg/dns/server/name_table_test.go b/pkg/dns/server/name_table_test.go
index 76d9ff4de8..a37e941203 100644
--- a/pkg/dns/server/name_table_test.go
+++ b/pkg/dns/server/name_table_test.go
@@ -104,6 +104,23 @@ func TestNameTable(t *testing.T) {
 		},
 	}
 
+	headlessServiceForServiceEntry := &model.Service{
+		Hostname:    host.Name("foo.bar.com"),
+		Address:     constants.UnspecifiedIP,
+		ClusterVIPs: make(map[cluster.ID]string),
+		Ports: model.PortList{&model.Port{
+			Name:     "tcp-port",
+			Port:     9000,
+			Protocol: protocol.TCP,
+		}},
+		Resolution: model.Passthrough,
+		Attributes: model.ServiceAttributes{
+			Name:            "foo.bar.com",
+			Namespace:       "testns",
+			ServiceRegistry: provider.External,
+		},
+	}
+
 	wildcardService := &model.Service{
 		Hostname:    host.Name("*.testns.svc.cluster.local"),
 		Address:     "172.10.10.10",
@@ -169,6 +186,17 @@ func TestNameTable(t *testing.T) {
 	cpush := model.NewPushContext()
 	wpush.AddPublicServices([]*model.Service{cidrService})
 
+	sepush := model.NewPushContext()
+	sepush.AddPublicServices([]*model.Service{headlessServiceForServiceEntry})
+	sepush.AddServiceInstances(headlessServiceForServiceEntry,
+		makeServiceInstances(pod1, headlessServiceForServiceEntry, "", ""))
+	sepush.AddServiceInstances(headlessServiceForServiceEntry,
+		makeServiceInstances(pod2, headlessServiceForServiceEntry, "", ""))
+	sepush.AddServiceInstances(headlessServiceForServiceEntry,
+		makeServiceInstances(pod3, headlessServiceForServiceEntry, "", ""))
+	sepush.AddServiceInstances(headlessServiceForServiceEntry,
+		makeServiceInstances(pod4, headlessServiceForServiceEntry, "", ""))
+
 	cases := []struct {
 		name                       string
 		proxy                      *model.Proxy
@@ -392,6 +420,45 @@ func TestNameTable(t *testing.T) {
 				},
 			},
 		},
+		{
+			name:  "service entry with resolution = NONE",
+			proxy: proxy,
+			push:  sepush,
+			expectedNameTable: &dnsProto.NameTable{
+				Table: map[string]*dnsProto.NameTable_NameInfo{
+					"foo.bar.com": {
+						Ips:      []string{"1.2.3.4", "9.6.7.8", "19.6.7.8", "9.16.7.8"},
+						Registry: "External",
+					},
+				},
+			},
+		},
+		{
+			name:  "service entry with resolution = NONE with network isolation",
+			proxy: nw1proxy,
+			push:  sepush,
+			expectedNameTable: &dnsProto.NameTable{
+				Table: map[string]*dnsProto.NameTable_NameInfo{
+					"foo.bar.com": {
+						Ips:      []string{"1.2.3.4", "19.6.7.8", "9.16.7.8"},
+						Registry: "External",
+					},
+				},
+			},
+		},
+		{
+			name:  "multi cluster service entry with resolution = NONE",
+			proxy: cl1proxy,
+			push:  sepush,
+			expectedNameTable: &dnsProto.NameTable{
+				Table: map[string]*dnsProto.NameTable_NameInfo{
+					"foo.bar.com": {
+						Ips:      []string{"1.2.3.4", "19.6.7.8", "9.16.7.8"},
+						Registry: "External",
+					},
+				},
+			},
+		},
 	}
 	for _, tt := range cases {
 		t.Run(tt.name, func(t *testing.T) {