CVE-2021-32783: Externally Controlled Reference to a Resource in Another Sphere (CWE-610)
value: Contour is a Kubernetes ingress controller using Envoy proxy. In Contour before version 1.17.1 a specially crafted ExternalName type Service may be used to access Envoy's admin interface, which Contour normally prevents from access outside the Envoy container. This can be used to shut down Envoy remotely (a denial of service), or to expose the existence of any Secret that Envoy is using for its configuration, including most notably TLS Keypairs. However, it *cannot* be used to get the *content* of those secrets. Since this attack allows access to the administration interface, a variety of administration options are available, such as shutting down the Envoy or draining traffic. In general, the Envoy admin interface cannot easily be used for making changes to the cluster, in-flight requests, or backend services, but it could be used to shut down or drain Envoy, change traffic routing, or to retrieve secret metadata, as mentioned above. The issue will be addressed in Contour v1.18.0 and a cherry-picked patch release, v1.17.1, has been released to cover users who cannot upgrade at this time. For more details refer to the linked GitHub Security Advisory.
CWE description: The product uses an externally controlled name or reference that resolves to a resource that is outside of the intended control sphere.
CVSS v3 base score: 8.5
Published: 2021-07-23T22:15Z
Repository: https://github.com/projectcontour/contour
Language: Go
Files changed: cmd/contour/serve.go, examples/contour/01-contour-config.yaml, examples/render/contour-gateway.yaml, examples/render/contour.yaml, go.mod, internal/dag/accessors.go, internal/dag/accessors_test.go, internal/dag/builder_test.go, internal/dag/extension_processor.go, internal/dag/gatewayapi_processor.go, internal/dag/httpproxy_processor.go, internal/dag/ingress_processor.go, internal/dag/status_test.go, internal/featuretests/v3/externalname_test.go, internal/featuretests/v3/headerpolicy_test.go, pkg/config/parameters.go, site/content/docs/main/configuration.md, test/e2e/httpproxy/018_external_name_test.go, test/e2e/httpproxy/httpproxy_test.go
Commit date: 2021-07-22 12:04:46 -0600
Commit message: cherrypicks for v1.17.1 (#3909)

* Merge pull request from GHSA-5ph6-qq5x-7jwc

Signed-off-by: Nick Young <ynick@vmware.com>

* Fix spelling and lint errors that slipped into the ExternalName PR (#3908)

Signed-off-by: Nick Young <ynick@vmware.com>

Co-authored-by: Nick Young <ynick@vmware.com>
Diff: diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go
index 6a384e52..99dee24b 100644
--- a/cmd/contour/serve.go
+++ b/cmd/contour/serve.go
@@ -712,33 +712,39 @@ func getDAGBuilder(ctx *serveContext, clients *k8s.Clients, clientCert, fallback
 	if ctx.Config.Policy.ResponseHeadersPolicy.Remove != nil {
 		responseHeadersPolicy.Remove = make([]string, 0, len(ctx.Config.Policy.ResponseHeadersPolicy.Remove))
 		responseHeadersPolicy.Remove = append(responseHeadersPolicy.Remove, ctx.Config.Policy.ResponseHeadersPolicy.Remove...)
 	}
 
+	log.Debugf("EnableExternalNameService is set to %t", ctx.Config.EnableExternalNameService)
 	// Get the appropriate DAG processors.
 	dagProcessors := []dag.Processor{
 		&dag.IngressProcessor{
-			FieldLogger:       log.WithField("context", "IngressProcessor"),
-			ClientCertificate: clientCert,
+			EnableExternalNameService: ctx.Config.EnableExternalNameService,
+			FieldLogger:               log.WithField("context", "IngressProcessor"),
+			ClientCertificate:         clientCert,
 		},
 		&dag.ExtensionServiceProcessor{
+			// Note that ExtensionService does not support ExternalName, if it does get added,
+			// need to bring EnableExternalNameService in here too.
 			FieldLogger:       log.WithField("context", "ExtensionServiceProcessor"),
 			ClientCertificate: clientCert,
 		},
 		&dag.HTTPProxyProcessor{
-			DisablePermitInsecure: ctx.Config.DisablePermitInsecure,
-			FallbackCertificate:   fallbackCert,
-			DNSLookupFamily:       ctx.Config.Cluster.DNSLookupFamily,
-			ClientCertificate:     clientCert,
-			RequestHeadersPolicy:  &requestHeadersPolicy,
-			ResponseHeadersPolicy: &responseHeadersPolicy,
+			EnableExternalNameService: ctx.Config.EnableExternalNameService,
+			DisablePermitInsecure:     ctx.Config.DisablePermitInsecure,
+			FallbackCertificate:       fallbackCert,
+			DNSLookupFamily:           ctx.Config.Cluster.DNSLookupFamily,
+			ClientCertificate:         clientCert,
+			RequestHeadersPolicy:      &requestHeadersPolicy,
+			ResponseHeadersPolicy:     &responseHeadersPolicy,
 		},
 	}
 
 	if ctx.Config.GatewayConfig != nil && clients.ResourcesExist(k8s.GatewayAPIResources()...) {
 		dagProcessors = append(dagProcessors, &dag.GatewayAPIProcessor{
-			FieldLogger: log.WithField("context", "GatewayAPIProcessor"),
+			EnableExternalNameService: ctx.Config.EnableExternalNameService,
+			FieldLogger:               log.WithField("context", "GatewayAPIProcessor"),
 		})
 	}
 
 	// The listener processor has to go last since it looks at
 	// the output of the other processors.
diff --git a/examples/contour/01-contour-config.yaml b/examples/contour/01-contour-config.yaml
index 496aad25..ee2989f7 100644
--- a/examples/contour/01-contour-config.yaml
+++ b/examples/contour/01-contour-config.yaml
@@ -49,10 +49,17 @@ data:
     #   namespace: projectcontour
     # The following config shows the defaults for the leader election.
     # leaderelection:
     #   configmap-name: leader-elect
     #   configmap-namespace: projectcontour
+    ####
+    # ExternalName Services are disabled by default due to CVE-2021-XXXXX
+    # You can re-enable them by setting this setting to `true`.
+    # This is not recommended without understanding the security implications.
+    # Please see the advisory at https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc for the details.
+    # enableExternalNameService: false
+    ## 
     ### Logging options
     # Default setting
     accesslog-format: envoy
     # To enable JSON logging in Envoy
     # accesslog-format: json
diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml
index fd3913a5..50827a86 100644
--- a/examples/render/contour-gateway.yaml
+++ b/examples/render/contour-gateway.yaml
@@ -86,10 +86,17 @@ data:
       namespace: projectcontour
     # The following config shows the defaults for the leader election.
     # leaderelection:
     #   configmap-name: leader-elect
     #   configmap-namespace: projectcontour
+    ####
+    # ExternalName Services are disabled by default due to CVE-2021-XXXXX
+    # You can re-enable them by setting this setting to `true`.
+    # This is not recommended without understanding the security implications.
+    # Please see the advisory at https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc for the details.
+    # enableExternalNameService: false
+    ##
     ### Logging options
     # Default setting
     accesslog-format: envoy
     # To enable JSON logging in Envoy
     # accesslog-format: json
diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml
index e6c0fdd9..cd04af3f 100644
--- a/examples/render/contour.yaml
+++ b/examples/render/contour.yaml
@@ -83,10 +83,17 @@ data:
     #   namespace: projectcontour
     # The following config shows the defaults for the leader election.
     # leaderelection:
     #   configmap-name: leader-elect
     #   configmap-namespace: projectcontour
+    ####
+    # ExternalName Services are disabled by default due to CVE-2021-XXXXX
+    # You can re-enable them by setting this setting to `true`.
+    # This is not recommended without understanding the security implications.
+    # Please see the advisory at https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc for the details.
+    # enableExternalNameService: false
+    ##
     ### Logging options
     # Default setting
     accesslog-format: envoy
     # To enable JSON logging in Envoy
     # accesslog-format: json
diff --git a/go.mod b/go.mod
index 7b533cb5..54bb5ded 100644
--- a/go.mod
+++ b/go.mod
@@ -3,10 +3,11 @@ module github.com/projectcontour/contour
 go 1.15
 
 require (
 	github.com/ahmetb/gen-crd-api-reference-docs v0.3.0
 	github.com/bombsimon/logrusr v1.0.0
+	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/envoyproxy/go-control-plane v0.9.9-0.20210111201334-f1f47757da33
 	github.com/go-logr/logr v0.4.0
 	github.com/golang/protobuf v1.5.2
 	github.com/google/go-cmp v0.5.5
 	github.com/google/uuid v1.1.2
diff --git a/internal/dag/accessors.go b/internal/dag/accessors.go
index 9f61e412..1a9cb987 100644
--- a/internal/dag/accessors.go
+++ b/internal/dag/accessors.go
@@ -49,16 +49,21 @@ func (dag *DAG) GetService(meta types.NamespacedName, port int32) *Service {
 }
 
 // EnsureService looks for a Kubernetes service in the cache matching the provided
 // namespace, name and port, and returns a DAG service for it. If a matching service
 // cannot be found in the cache, an error is returned.
-func (dag *DAG) EnsureService(meta types.NamespacedName, port intstr.IntOrString, cache *KubernetesCache) (*Service, error) {
+func (dag *DAG) EnsureService(meta types.NamespacedName, port intstr.IntOrString, cache *KubernetesCache, enableExternalNameSvc bool) (*Service, error) {
 	svc, svcPort, err := cache.LookupService(meta, port)
 	if err != nil {
 		return nil, err
 	}
 
+	err = validateExternalName(svc, enableExternalNameSvc)
+	if err != nil {
+		return nil, err
+	}
+
 	if dagSvc := dag.GetService(k8s.NamespacedNameOf(svc), svcPort.Port); dagSvc != nil {
 		return dagSvc, nil
 	}
 
 	dagSvc := &Service{
@@ -76,10 +81,42 @@ func (dag *DAG) EnsureService(meta types.NamespacedName, port intstr.IntOrString
 		ExternalName:       externalName(svc),
 	}
 	return dagSvc, nil
 }
 
+func validateExternalName(svc *v1.Service, enableExternalNameSvc bool) error {
+
+	// If this isn't an ExternalName Service, we're all good here.
+	en := externalName(svc)
+	if en == "" {
+		return nil
+	}
+
+	// If ExternalNames are disabled, then we don't want to add this to the DAG.
+	if !enableExternalNameSvc {
+		return fmt.Errorf("%s/%s is an ExternalName service, these are not currently enabled. See the config.enableExternalNameService config file setting", svc.Namespace, svc.Name)
+	}
+
+	// Check against a list of known localhost names, using a map to approximate a set.
+	// TODO(youngnick) This is a very porous hack, and we should probably look into doing a DNS
+	// lookup to check what the externalName resolves to, but I'm worried about the
+	// performance impact of doing one or more DNS lookups per DAG run, so we're
+	// going to go with a specific blocklist for now.
+	localhostNames := map[string]struct{}{
+		"localhost":               {},
+		"localhost.localdomain":   {},
+		"local.projectcontour.io": {},
+	}
+
+	_, localhost := localhostNames[en]
+	if localhost {
+		return fmt.Errorf("%s/%s is an ExternalName service that points to localhost, this is not allowed", svc.Namespace, svc.Name)
+	}
+
+	return nil
+}
+
 func upstreamProtocol(svc *v1.Service, port v1.ServicePort) string {
 	up := annotation.ParseUpstreamProtocols(svc.Annotations)
 	protocol := up[port.Name]
 	if protocol == "" {
 		protocol = up[strconv.Itoa(int(port.Port))]
diff --git a/internal/dag/accessors_test.go b/internal/dag/accessors_test.go
index 3ab9fd15..48cd48e5 100644
--- a/internal/dag/accessors_test.go
+++ b/internal/dag/accessors_test.go
@@ -38,19 +38,57 @@ func TestBuilderLookupService(t *testing.T) {
 				Port:       8080,
 				TargetPort: intstr.FromInt(8080),
 			}},
 		},
 	}
+
+	externalNameValid := &v1.Service{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "externalnamevalid",
+			Namespace: "default",
+		},
+		Spec: v1.ServiceSpec{
+			Type:         v1.ServiceTypeExternalName,
+			ExternalName: "external.projectcontour.io",
+			Ports: []v1.ServicePort{{
+				Name:       "http",
+				Protocol:   "TCP",
+				Port:       80,
+				TargetPort: intstr.FromInt(80),
+			}},
+		},
+	}
+
+	externalNameLocalhost := &v1.Service{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "externalnamelocalhost",
+			Namespace: "default",
+		},
+		Spec: v1.ServiceSpec{
+			Type:         v1.ServiceTypeExternalName,
+			ExternalName: "localhost",
+			Ports: []v1.ServicePort{{
+				Name:       "http",
+				Protocol:   "TCP",
+				Port:       80,
+				TargetPort: intstr.FromInt(80),
+			}},
+		},
+	}
+
 	services := map[types.NamespacedName]*v1.Service{
-		{Name: "service1", Namespace: "default"}: s1,
+		{Name: "service1", Namespace: "default"}:              s1,
+		{Name: "externalnamevalid", Namespace: "default"}:     externalNameValid,
+		{Name: "externalnamelocalhost", Namespace: "default"}: externalNameLocalhost,
 	}
 
 	tests := map[string]struct {
 		types.NamespacedName
-		port    intstr.IntOrString
-		want    *Service
-		wantErr error
+		port                  intstr.IntOrString
+		enableExternalNameSvc bool
+		want                  *Service
+		wantErr               error
 	}{
 		"lookup service by port number": {
 			NamespacedName: types.NamespacedName{Name: "service1", Namespace: "default"},
 			port:           intstr.FromInt(8080),
 			want:           service(s1),
@@ -78,10 +116,40 @@ func TestBuilderLookupService(t *testing.T) {
 		"when port does not exist an error is returned": {
 			NamespacedName: types.NamespacedName{Name: "service1", Namespace: "default"},
 			port:           intstr.FromString("9999"),
 			wantErr:        errors.New(`port "9999" on service "default/service1" not matched`),
 		},
+		"When ExternalName Services are not disabled no error is returned": {
+			NamespacedName: types.NamespacedName{Name: "externalnamevalid", Namespace: "default"},
+			port:           intstr.FromString("80"),
+			want: &Service{
+				Weighted: WeightedService{
+					Weight:           1,
+					ServiceName:      "externalnamevalid",
+					ServiceNamespace: "default",
+					ServicePort: v1.ServicePort{
+						Name:       "http",
+						Protocol:   "TCP",
+						Port:       80,
+						TargetPort: intstr.FromInt(80),
+					},
+				},
+				ExternalName: "external.projectcontour.io",
+			},
+			enableExternalNameSvc: true,
+		},
+		"When ExternalName Services are disabled an error is returned": {
+			NamespacedName: types.NamespacedName{Name: "externalnamevalid", Namespace: "default"},
+			port:           intstr.FromString("80"),
+			wantErr:        errors.New(`default/externalnamevalid is an ExternalName service, these are not currently enabled. See the config.enableExternalNameService config file setting`),
+		},
+		"When ExternalName Services are enabled but a localhost ExternalName is used an error is returned": {
+			NamespacedName:        types.NamespacedName{Name: "externalnamelocalhost", Namespace: "default"},
+			port:                  intstr.FromString("80"),
+			wantErr:               errors.New(`default/externalnamelocalhost is an ExternalName service that points to localhost, this is not allowed`),
+			enableExternalNameSvc: true,
+		},
 	}
 
 	for name, tc := range tests {
 		t.Run(name, func(t *testing.T) {
 			b := Builder{
@@ -91,11 +159,11 @@ func TestBuilderLookupService(t *testing.T) {
 				},
 			}
 
 			var dag DAG
 
-			got, gotErr := dag.EnsureService(tc.NamespacedName, tc.port, &b.Source)
+			got, gotErr := dag.EnsureService(tc.NamespacedName, tc.port, &b.Source, tc.enableExternalNameSvc)
 			assert.Equal(t, tc.want, got)
 			assert.Equal(t, tc.wantErr, gotErr)
 		})
 	}
 }
diff --git a/internal/dag/builder_test.go b/internal/dag/builder_test.go
index 5fe5a631..19f438d0 100644
--- a/internal/dag/builder_test.go
+++ b/internal/dag/builder_test.go
@@ -6534,10 +6534,29 @@ func TestDAGInsert(t *testing.T) {
 				}},
 			}},
 		},
 	}
 
+	ingressExternalNameService := &networking_v1.Ingress{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "externalname",
+			Namespace: "default",
+		},
+		Spec: networking_v1.IngressSpec{
+			Rules: []networking_v1.IngressRule{{
+				Host: "example.com",
+				IngressRuleValue: networking_v1.IngressRuleValue{
+					HTTP: &networking_v1.HTTPIngressRuleValue{
+						Paths: []networking_v1.HTTPIngressPath{{
+							Backend: *backendv1(s14.GetName(), intstr.FromInt(80)),
+						}},
+					},
+				},
+			}},
+		},
+	}
+
 	proxyExternalNameService := &contour_api_v1.HTTPProxy{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      "example-com",
 			Namespace: "default",
 		},
@@ -6580,10 +6599,11 @@ func TestDAGInsert(t *testing.T) {
 	}
 
 	tests := map[string]struct {
 		objs                         []interface{}
 		disablePermitInsecure        bool
+		enableExternalNameSvc        bool
 		fallbackCertificateName      string
 		fallbackCertificateNamespace string
 		want                         []Vertex
 	}{
 		"ingressv1: insert ingress w/ default backend w/o matching service": {
@@ -8978,15 +8998,52 @@ func TestDAGInsert(t *testing.T) {
 						virtualhost("example.com", prefixroute("/", service(s2a))),
 					),
 				},
 			),
 		},
+		"insert ingress with externalName service": {
+			objs: []interface{}{
+				ingressExternalNameService,
+				s14,
+			},
+			enableExternalNameSvc: true,
+			want: listeners(
+				&Listener{
+					Port: 80,
+					VirtualHosts: virtualhosts(
+						virtualhost("example.com", &Route{
+							PathMatchCondition: prefixString("/"),
+							Clusters: []*Cluster{{
+								Upstream: &Service{
+									ExternalName: "externalservice.io",
+									Weighted: WeightedService{
+										Weight:           1,
+										ServiceName:      s14.Name,
+										ServiceNamespace: s14.Namespace,
+										ServicePort:      s14.Spec.Ports[0],
+									},
+								},
+							}},
+						}),
+					),
+				},
+			),
+		},
+		"insert ingress with externalName service, but externalName services disabled": {
+			objs: []interface{}{
+				ingressExternalNameService,
+				s14,
+			},
+			enableExternalNameSvc: false,
+			want:                  listeners(),
+		},
 		"insert proxy with externalName service": {
 			objs: []interface{}{
 				proxyExternalNameService,
 				s14,
 			},
+			enableExternalNameSvc: true,
 			want: listeners(
 				&Listener{
 					Port: 80,
 					VirtualHosts: virtualhosts(
 						virtualhost("example.com", &Route{
@@ -9012,10 +9069,11 @@ func TestDAGInsert(t *testing.T) {
 			objs: []interface{}{
 				tcpProxyExternalNameService,
 				s14,
 				sec1,
 			},
+			enableExternalNameSvc: true,
 			want: listeners(
 				&Listener{
 					Port: 443,
 					VirtualHosts: virtualhosts(
 						&SecureVirtualHost{
@@ -9071,10 +9129,11 @@ func TestDAGInsert(t *testing.T) {
 		"insert proxy with replace header policy - route - host header - externalName": {
 			objs: []interface{}{
 				proxyReplaceHostHeaderRoute,
 				s14,
 			},
+			enableExternalNameSvc: true,
 			want: listeners(
 				&Listener{
 					Port: 80,
 					VirtualHosts: virtualhosts(
 						virtualhost("example.com", &Route{
@@ -9109,11 +9168,12 @@ func TestDAGInsert(t *testing.T) {
 		"insert proxy with replace header policy - service - host header - externalName": {
 			objs: []interface{}{
 				proxyReplaceHostHeaderService,
 				s14,
 			},
-			want: listeners(),
+			enableExternalNameSvc: true,
+			want:                  listeners(),
 		},
 		"insert proxy with response header policy - route - host header": {
 			objs: []interface{}{
 				proxy111,
 				s9,
@@ -9752,14 +9812,16 @@ func TestDAGInsert(t *testing.T) {
 				Source: KubernetesCache{
 					FieldLogger: fixture.NewTestLogger(t),
 				},
 				Processors: []Processor{
 					&IngressProcessor{
-						FieldLogger: fixture.NewTestLogger(t),
+						FieldLogger:               fixture.NewTestLogger(t),
+						EnableExternalNameService: tc.enableExternalNameSvc,
 					},
 					&HTTPProxyProcessor{
-						DisablePermitInsecure: tc.disablePermitInsecure,
+						EnableExternalNameService: tc.enableExternalNameSvc,
+						DisablePermitInsecure:     tc.disablePermitInsecure,
 						FallbackCertificate: &types.NamespacedName{
 							Name:      tc.fallbackCertificateName,
 							Namespace: tc.fallbackCertificateNamespace,
 						},
 					},
diff --git a/internal/dag/extension_processor.go b/internal/dag/extension_processor.go
index 18551b37..2f7cbe5d 100644
--- a/internal/dag/extension_processor.go
+++ b/internal/dag/extension_processor.go
@@ -175,10 +175,12 @@ func (p *ExtensionServiceProcessor) buildExtensionService(
 				"unresolved service %q: %s", svcName, err)
 			continue
 		}
 
 		// TODO(jpeach): Add ExternalName support in https://github.com/projectcontour/contour/issues/2875.
+		// TODO(youngnick): If ExternalName support is added, we must pass down the EnableExternalNameService bool
+		// and check it first.
 		if svc.Spec.ExternalName != "" {
 			validCondition.AddErrorf(contour_api_v1.ConditionTypeServiceError, "UnsupportedServiceType",
 				"Service %q is of unsupported type %q.", svcName, corev1.ServiceTypeExternalName)
 			continue
 		}
diff --git a/internal/dag/gatewayapi_processor.go b/internal/dag/gatewayapi_processor.go
index 3d15c5e3..3820aa6b 100644
--- a/internal/dag/gatewayapi_processor.go
+++ b/internal/dag/gatewayapi_processor.go
@@ -45,10 +45,15 @@ const (
 type GatewayAPIProcessor struct {
 	logrus.FieldLogger
 
 	dag    *DAG
 	source *KubernetesCache
+
+	// EnableExternalNameService allows processing of ExternalNameServices
+	// This is normally disabled for security reasons.
+	// See https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc for details.
+	EnableExternalNameService bool
 }
 
 // matchConditions holds match rules.
 type matchConditions struct {
 	pathMatchConditions  []MatchCondition
@@ -742,13 +747,13 @@ func (p *GatewayAPIProcessor) validateForwardTo(serviceName *string, port *gatew
 	}
 
 	meta := types.NamespacedName{Name: *serviceName, Namespace: namespace}
 
 	// TODO: Refactor EnsureService to take an int32 so conversion to intstr is not needed.
-	service, err := p.dag.EnsureService(meta, intstr.FromInt(int(*port)), p.source)
+	service, err := p.dag.EnsureService(meta, intstr.FromInt(int(*port)), p.source, p.EnableExternalNameService)
 	if err != nil {
-		return nil, fmt.Errorf("service %q does not exist", meta.Name)
+		return nil, fmt.Errorf("service %q is invalid: %s", meta.Name, err)
 	}
 
 	return service, nil
 }
 
diff --git a/internal/dag/httpproxy_processor.go b/internal/dag/httpproxy_processor.go
index 454e50e1..6af53c0b 100644
--- a/internal/dag/httpproxy_processor.go
+++ b/internal/dag/httpproxy_processor.go
@@ -55,10 +55,15 @@ type HTTPProxyProcessor struct {
 	// FallbackCertificate is the optional identifier of the
 	// TLS secret to use by default when SNI is not set on a
 	// request.
 	FallbackCertificate *types.NamespacedName
 
+	// EnableExternalNameService allows processing of ExternalNameServices
+	// This is normally disabled for security reasons.
+	// See https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc for details.
+	EnableExternalNameService bool
+
 	// DNSLookupFamily defines how external names are looked up
 	// When configured as V4, the DNS resolver will only perform a lookup
 	// for addresses in the IPv4 family. If V6 is configured, the DNS resolver
 	// will only perform a lookup for addresses in the IPv6 family.
 	// If AUTO is configured, the DNS resolver will first perform a lookup
@@ -561,11 +566,11 @@ func (p *HTTPProxyProcessor) computeRoutes(
 				validCond.AddErrorf(contour_api_v1.ConditionTypeServiceError, "ServicePortInvalid",
 					"service %q: port must be in the range 1-65535", service.Name)
 				return nil
 			}
 			m := types.NamespacedName{Name: service.Name, Namespace: proxy.Namespace}
-			s, err := p.dag.EnsureService(m, intstr.FromInt(service.Port), p.source)
+			s, err := p.dag.EnsureService(m, intstr.FromInt(service.Port), p.source, p.EnableExternalNameService)
 			if err != nil {
 				validCond.AddErrorf(contour_api_v1.ConditionTypeServiceError, "ServiceUnresolvedReference",
 					"Spec.Routes unresolved service reference: %s", err)
 				return nil
 			}
@@ -686,13 +691,13 @@ func (p *HTTPProxyProcessor) processHTTPProxyTCPProxy(validCond *contour_api_v1.
 
 	if len(tcpproxy.Services) > 0 {
 		var proxy TCPProxy
 		for _, service := range httpproxy.Spec.TCPProxy.Services {
 			m := types.NamespacedName{Name: service.Name, Namespace: httpproxy.Namespace}
-			s, err := p.dag.EnsureService(m, intstr.FromInt(service.Port), p.source)
+			s, err := p.dag.EnsureService(m, intstr.FromInt(service.Port), p.source, p.EnableExternalNameService)
 			if err != nil {
-				validCond.AddErrorf(contour_api_v1.ConditionTypeTCPProxyError, "UnresolvedServiceRef",
+				validCond.AddErrorf(contour_api_v1.ConditionTypeTCPProxyError, "ServiceUnresolvedReference",
 					"Spec.TCPProxy unresolved service reference: %s", err)
 				return false
 			}
 
 			// Determine the protocol to use to speak to this Cluster.
diff --git a/internal/dag/ingress_processor.go b/internal/dag/ingress_processor.go
index 8823cc81..ea6b6cfd 100644
--- a/internal/dag/ingress_processor.go
+++ b/internal/dag/ingress_processor.go
@@ -35,10 +35,15 @@ type IngressProcessor struct {
 	source *KubernetesCache
 
 	// ClientCertificate is the optional identifier of the TLS secret containing client certificate and
 	// private key to be used when establishing TLS connection to upstream cluster.
 	ClientCertificate *types.NamespacedName
+
+	// EnableExternalNameService allows processing of ExternalNameServices
+	// This is normally disabled for security reasons.
+	// See https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc for details.
+	EnableExternalNameService bool
 }
 
 // Run translates Ingresses into DAG objects and
 // adds them to the DAG.
 func (p *IngressProcessor) Run(dag *DAG, source *KubernetesCache) {
@@ -142,11 +147,11 @@ func (p *IngressProcessor) computeIngressRule(ing *networking_v1.Ingress, rule n
 			port = intstr.FromString(be.Service.Port.Name)
 		} else {
 			port = intstr.FromInt(int(be.Service.Port.Number))
 		}
 
-		s, err := p.dag.EnsureService(m, port, p.source)
+		s, err := p.dag.EnsureService(m, port, p.source, p.EnableExternalNameService)
 		if err != nil {
 			p.WithError(err).
 				WithField("name", ing.GetName()).
 				WithField("namespace", ing.GetNamespace()).
 				WithField("service", be.Service.Name).
diff --git a/internal/dag/status_test.go b/internal/dag/status_test.go
index edea4176..ff648b3a 100644
--- a/internal/dag/status_test.go
+++ b/internal/dag/status_test.go
@@ -1756,11 +1756,11 @@ func TestDAGStatus(t *testing.T) {
 
 	run(t, "httpproxy w/ tcpproxy w/ missing service", testcase{
 		objs: []interface{}{proxyTCPInvalidMissingService},
 		want: map[types.NamespacedName]contour_api_v1.DetailedCondition{
 			{Name: proxyTCPInvalidMissingService.Name, Namespace: proxyTCPInvalidMissingService.Namespace}: fixture.NewValidCondition().
-				WithError(contour_api_v1.ConditionTypeTCPProxyError, "UnresolvedServiceRef", `Spec.TCPProxy unresolved service reference: service "roots/not-found" not found`),
+				WithError(contour_api_v1.ConditionTypeTCPProxyError, "ServiceUnresolvedReference", `Spec.TCPProxy unresolved service reference: service "roots/not-found" not found`),
 		},
 	})
 
 	proxyTCPInvalidPortNotMatched := &contour_api_v1.HTTPProxy{
 		ObjectMeta: metav1.ObjectMeta{
@@ -1785,11 +1785,11 @@ func TestDAGStatus(t *testing.T) {
 
 	run(t, "httpproxy w/ tcpproxy w/ service missing port", testcase{
 		objs: []interface{}{proxyTCPInvalidPortNotMatched, fixture.ServiceRootsKuard},
 		want: map[types.NamespacedName]contour_api_v1.DetailedCondition{
 			{Name: proxyTCPInvalidPortNotMatched.Name, Namespace: proxyTCPInvalidPortNotMatched.Namespace}: fixture.NewValidCondition().
-				WithError(contour_api_v1.ConditionTypeTCPProxyError, "UnresolvedServiceRef", `Spec.TCPProxy unresolved service reference: port "9999" on service "roots/kuard" not matched`),
+				WithError(contour_api_v1.ConditionTypeTCPProxyError, "ServiceUnresolvedReference", `Spec.TCPProxy unresolved service reference: port "9999" on service "roots/kuard" not matched`),
 		},
 	})
 
 	proxyTCPInvalidMissingTLS := &contour_api_v1.HTTPProxy{
 		ObjectMeta: metav1.ObjectMeta{
@@ -2929,11 +2929,11 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) {
 			Conditions: map[gatewayapi_v1alpha1.RouteConditionType]metav1.Condition{
 				status.ConditionResolvedRefs: {
 					Type:    string(status.ConditionResolvedRefs),
 					Status:  contour_api_v1.ConditionFalse,
 					Reason:  string(status.ReasonDegraded),
-					Message: "service \"invalid-one\" does not exist, service \"invalid-two\" does not exist",
+					Message: "service \"invalid-one\" is invalid: service \"default/invalid-one\" not found, service \"invalid-two\" is invalid: service \"default/invalid-two\" not found",
 				},
 				gatewayapi_v1alpha1.ConditionRouteAdmitted: {
 					Type:    string(gatewayapi_v1alpha1.ConditionRouteAdmitted),
 					Status:  contour_api_v1.ConditionFalse,
 					Reason:  string(status.ReasonErrorsExist),
@@ -3659,11 +3659,11 @@ func TestGatewayAPITLSRouteDAGStatus(t *testing.T) {
 				Conditions: map[gatewayapi_v1alpha1.RouteConditionType]metav1.Condition{
 					status.ConditionResolvedRefs: {
 						Type:    string(status.ConditionResolvedRefs),
 						Status:  contour_api_v1.ConditionFalse,
 						Reason:  string(status.ReasonDegraded),
-						Message: "service \"invalid-one\" does not exist, service \"invalid-two\" does not exist",
+						Message: "service \"invalid-one\" is invalid: service \"default/invalid-one\" not found, service \"invalid-two\" is invalid: service \"default/invalid-two\" not found",
 					},
 					gatewayapi_v1alpha1.ConditionRouteAdmitted: {
 						Type:    string(gatewayapi_v1alpha1.ConditionRouteAdmitted),
 						Status:  contour_api_v1.ConditionFalse,
 						Reason:  "ErrorsExist",
diff --git a/internal/featuretests/v3/externalname_test.go b/internal/featuretests/v3/externalname_test.go
index 1e9e74ba..0ddfe531 100644
--- a/internal/featuretests/v3/externalname_test.go
+++ b/internal/featuretests/v3/externalname_test.go
@@ -14,11 +14,14 @@
 package v3
 
 import (
 	"testing"
 
+	"github.com/projectcontour/contour/internal/contour"
+	"github.com/projectcontour/contour/internal/dag"
 	"github.com/projectcontour/contour/internal/featuretests"
+	"github.com/sirupsen/logrus"
 
 	envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
 	envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
 	envoy_extensions_upstream_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3"
 	envoy_discovery_v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
@@ -35,11 +38,11 @@ import (
 )
 
 // Assert that services of type v1.ServiceTypeExternalName can be
 // referenced by an Ingress, or HTTPProxy document.
 func TestExternalNameService(t *testing.T) {
-	rh, c, done := setup(t)
+	rh, c, done := setup(t, enableExternalNameService(t))
 	defer done()
 
 	s1 := fixture.NewService("kuard").
 		WithSpec(v1.ServiceSpec{
 			Ports: []v1.ServicePort{{
@@ -315,5 +318,27 @@ func TestExternalNameService(t *testing.T) {
 				},
 			),
 		),
 	})
 }
+
+func enableExternalNameService(t *testing.T) func(eh *contour.EventHandler) {
+	return func(eh *contour.EventHandler) {
+
+		log := fixture.NewTestLogger(t)
+		log.SetLevel(logrus.DebugLevel)
+
+		eh.Builder.Processors = []dag.Processor{
+			&dag.IngressProcessor{
+				EnableExternalNameService: true,
+				FieldLogger:               log.WithField("context", "IngressProcessor"),
+			},
+			&dag.HTTPProxyProcessor{
+				EnableExternalNameService: true,
+			},
+			&dag.ExtensionServiceProcessor{
+				FieldLogger: log.WithField("context", "ExtensionServiceProcessor"),
+			},
+			&dag.ListenerProcessor{},
+		}
+	}
+}
diff --git a/internal/featuretests/v3/headerpolicy_test.go b/internal/featuretests/v3/headerpolicy_test.go
index a7ecb60d..3df4ff12 100644
--- a/internal/featuretests/v3/headerpolicy_test.go
+++ b/internal/featuretests/v3/headerpolicy_test.go
@@ -28,11 +28,14 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/intstr"
 )
 
 func TestHeaderPolicy_ReplaceHeader_HTTProxy(t *testing.T) {
-	rh, c, done := setup(t)
+	// Enable ExternalName processing here because
+	// we need to check that host rewrites work in combination
+	// with ExternalName.
+	rh, c, done := setup(t, enableExternalNameService(t))
 	defer done()
 
 	rh.OnAdd(fixture.NewService("svc1").
 		WithPorts(v1.ServicePort{Port: 80, TargetPort: intstr.FromInt(8080)}),
 	)
diff --git a/pkg/config/parameters.go b/pkg/config/parameters.go
index 4913f790..c207afc3 100644
--- a/pkg/config/parameters.go
+++ b/pkg/config/parameters.go
@@ -534,10 +534,15 @@ type Parameters struct {
 	// default behavior in case of failures. Please file an issue if failures
 	// are encountered.
 	// See: https://github.com/projectcontour/contour/issues/3221
 	DisableAllowChunkedLength bool `yaml:"disableAllowChunkedLength,omitempty"`
 
+	// EnableExternalNameService allows processing of ExternalNameServices
+	// Defaults to disabled for security reasons.
+	// TODO(youngnick): put a link to the issue and CVE here.
+	EnableExternalNameService bool `yaml:"enableExternalNameService,omitempty"`
+
 	// LeaderElection contains leader election parameters.
 	LeaderElection LeaderElectionParameters `yaml:"leaderelection,omitempty"`
 
 	// Timeouts holds various configurable timeouts that can
 	// be set in the config file.
diff --git a/site/content/docs/main/configuration.md b/site/content/docs/main/configuration.md
index 101450ae..1721215e 100644
--- a/site/content/docs/main/configuration.md
+++ b/site/content/docs/main/configuration.md
@@ -82,10 +82,11 @@ Where Contour settings can also be specified with command-line flags, the comman
 | network | NetworkConfig | | The [network configuration](#network-configuration). |
 | listener | ListenerConfig | | The [listener configuration](#listener-configuration). |
 | server | ServerConfig |  | The [server configuration](#server-configuration) for `contour serve` command. |
 | gateway | GatewayConfig |  | The [gateway-api Gateway configuration](#gateway-configuration). |
 | rateLimitService | RateLimitServiceConfig | | The [rate limit service configuration](#rate-limit-service-configuration). |
+| enableExternalNameService | boolean | `false` | Enable ExternalName Service processing. Enabling this has security implications. Please see the [advisory](https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc) for more details. |
 
 ### TLS Configuration
 
 The TLS configuration block can be used to configure default values for how
 Contour should provision TLS hosts.
diff --git a/test/e2e/httpproxy/018_external_name_test.go b/test/e2e/httpproxy/018_external_name_test.go
index 7ace01d5..e6da8993 100644
--- a/test/e2e/httpproxy/018_external_name_test.go
+++ b/test/e2e/httpproxy/018_external_name_test.go
@@ -77,11 +77,14 @@ func testExternalNameServiceInsecure(namespace string) {
 						},
 					},
 				},
 			},
 		}
-		f.CreateHTTPProxyAndWaitFor(p, httpProxyValid)
+		proxy, ok := f.CreateHTTPProxyAndWaitFor(p, httpProxyValid)
+		if !ok {
+			t.Fatalf("The HTTPProxy did not become valid, here are the Valid condition's Errors: %s", httpProxyErrors(proxy))
+		}
 
 		res, ok := f.HTTP.RequestUntil(&e2e.HTTPRequestOpts{
 			Host:      p.Spec.VirtualHost.Fqdn,
 			Condition: e2e.HasStatusCode(200),
 		})
@@ -144,11 +147,14 @@ func testExternalNameServiceTLS(namespace string) {
 						},
 					},
 				},
 			},
 		}
-		f.CreateHTTPProxyAndWaitFor(p, httpProxyValid)
+		proxy, ok := f.CreateHTTPProxyAndWaitFor(p, httpProxyValid)
+		if !ok {
+			t.Fatalf("The HTTPProxy did not become valid, here are the Valid condition's Errors: %s", httpProxyErrors(proxy))
+		}
 
 		res, ok := f.HTTP.RequestUntil(&e2e.HTTPRequestOpts{
 			Host:      p.Spec.VirtualHost.Fqdn,
 			Condition: e2e.HasStatusCode(200),
 		})
@@ -157,5 +163,65 @@ func testExternalNameServiceTLS(namespace string) {
 }
 
 func stringPtr(s string) *string {
 	return &s
 }
+
+func testExternalNameServiceLocalhostInvalid(namespace string) {
+	Specify("external name services with localhost are rejected", func() {
+		t := f.T()
+
+		f.Fixtures.Echo.Deploy(namespace, "ingress-conformance-echo")
+
+		externalNameService := &corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: namespace,
+				Name:      "external-name-service-localhost",
+			},
+			Spec: corev1.ServiceSpec{
+				Type: corev1.ServiceTypeExternalName,
+				// The unit tests test just `localhost`, so test another item from that
+				// list.
+				ExternalName: "localhost.localdomain",
+				Ports: []corev1.ServicePort{
+					{
+						Name: "http",
+						Port: 80,
+					},
+				},
+			},
+		}
+		require.NoError(t, f.Client.Create(context.TODO(), externalNameService))
+
+		p := &contourv1.HTTPProxy{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: namespace,
+				Name:      "external-name-proxy",
+			},
+			Spec: contourv1.HTTPProxySpec{
+				VirtualHost: &contourv1.VirtualHost{
+					Fqdn: "externalnameservice.projectcontour.io",
+				},
+				Routes: []contourv1.Route{
+					{
+						Services: []contourv1.Service{
+							{
+								Name: externalNameService.Name,
+								Port: 80,
+							},
+						},
+						RequestHeadersPolicy: &contourv1.HeadersPolicy{
+							Set: []contourv1.HeaderValue{
+								{
+									Name:  "Host",
+									Value: externalNameService.Spec.ExternalName,
+								},
+							},
+						},
+					},
+				},
+			},
+		}
+		_, ok := f.CreateHTTPProxyAndWaitFor(p, httpProxyValid)
+		require.Falsef(t, ok, "ExternalName with hostname %s was accepted by Contour.", externalNameService.Spec.ExternalName)
+	})
+}
diff --git a/test/e2e/httpproxy/httpproxy_test.go b/test/e2e/httpproxy/httpproxy_test.go
index 91e2cd32..a121aed9 100644
--- a/test/e2e/httpproxy/httpproxy_test.go
+++ b/test/e2e/httpproxy/httpproxy_test.go
@@ -18,10 +18,11 @@ package httpproxy
 import (
 	"context"
 	"fmt"
 	"testing"
 
+	"github.com/davecgh/go-spew/spew"
 	certmanagerv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
 	certmanagermetav1 "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	"github.com/onsi/gomega/gexec"
@@ -212,14 +213,36 @@ var _ = Describe("HTTPProxy", func() {
 
 	f.NamespacedTest("016-dynamic-headers", testDynamicHeaders)
 
 	f.NamespacedTest("017-host-header-rewrite", testHostHeaderRewrite)
 
-	f.NamespacedTest("018-external-name-service-insecure", testExternalNameServiceInsecure)
+	f.NamespacedTest("018-external-name-service-insecure", func(namespace string) {
+		Context("with ExternalName Services enabled", func() {
+			BeforeEach(func() {
+				contourConfig.EnableExternalNameService = true
+			})
+			testExternalNameServiceInsecure(namespace)
+		})
+	})
 
-	f.NamespacedTest("018-external-name-service-tls", testExternalNameServiceTLS)
+	f.NamespacedTest("018-external-name-service-tls", func(namespace string) {
+		Context("with ExternalName Services enabled", func() {
+			BeforeEach(func() {
+				contourConfig.EnableExternalNameService = true
+			})
+			testExternalNameServiceTLS(namespace)
+		})
+	})
 
+	f.NamespacedTest("018-external-name-service-localhost", func(namespace string) {
+		Context("with ExternalName Services enabled", func() {
+			BeforeEach(func() {
+				contourConfig.EnableExternalNameService = true
+			})
+			testExternalNameServiceLocalhostInvalid(namespace)
+		})
+	})
 	f.NamespacedTest("019-local-rate-limiting-vhost", testLocalRateLimitingVirtualHost)
 
 	f.NamespacedTest("019-local-rate-limiting-route", testLocalRateLimitingRoute)
 
 	Context("global rate limiting", func() {
@@ -276,7 +299,30 @@ descriptors:
 })
 
 // httpProxyValid returns true if the proxy has a .status.currentStatus
 // of "valid".
 func httpProxyValid(proxy *contourv1.HTTPProxy) bool {
-	return proxy != nil && proxy.Status.CurrentStatus == "valid"
+
+	if proxy == nil {
+		return false
+	}
+
+	if len(proxy.Status.Conditions) == 0 {
+		return false
+	}
+
+	cond := proxy.Status.GetConditionFor("Valid")
+	return cond.Status == "True"
+
+}
+
+// httpProxyErrors provides a pretty summary of any Errors on the HTTPProxy Valid condition.
+// If there are no errors, the return value will be empty.
+func httpProxyErrors(proxy *contourv1.HTTPProxy) string {
+	cond := proxy.Status.GetConditionFor("Valid")
+	errors := cond.Errors
+	if len(errors) > 0 {
+		return spew.Sdump(errors)
+	}
+
+	return ""
 }

Vulnerable code: 			FieldLogger:       log.WithField("context", "IngressProcessor"),
			ClientCertificate: clientCert,
			DisablePermitInsecure: ctx.Config.DisablePermitInsecure,
			FallbackCertificate:   fallbackCert,
			DNSLookupFamily:       ctx.Config.Cluster.DNSLookupFamily,
			ClientCertificate:     clientCert,
			RequestHeadersPolicy:  &requestHeadersPolicy,
			ResponseHeadersPolicy: &responseHeadersPolicy,
			FieldLogger: log.WithField("context", "GatewayAPIProcessor"),
func (dag *DAG) EnsureService(meta types.NamespacedName, port intstr.IntOrString, cache *KubernetesCache) (*Service, error) {
		{Name: "service1", Namespace: "default"}: s1,
		port    intstr.IntOrString
		want    *Service
		wantErr error
			got, gotErr := dag.EnsureService(tc.NamespacedName, tc.port, &b.Source)
			want: listeners(),
						FieldLogger: fixture.NewTestLogger(t),
						DisablePermitInsecure: tc.disablePermitInsecure,
	service, err := p.dag.EnsureService(meta, intstr.FromInt(int(*port)), p.source)
		return nil, fmt.Errorf("service %q does not exist", meta.Name)
			s, err := p.dag.EnsureService(m, intstr.FromInt(service.Port), p.source)
			s, err := p.dag.EnsureService(m, intstr.FromInt(service.Port), p.source)
				validCond.AddErrorf(contour_api_v1.ConditionTypeTCPProxyError, "UnresolvedServiceRef",
		s, err := p.dag.EnsureService(m, port, p.source)
				WithError(contour_api_v1.ConditionTypeTCPProxyError, "UnresolvedServiceRef", `Spec.TCPProxy unresolved service reference: service "roots/not-found" not found`),
				WithError(contour_api_v1.ConditionTypeTCPProxyError, "UnresolvedServiceRef", `Spec.TCPProxy unresolved service reference: port "9999" on service "roots/kuard" not matched`),
					Message: "service \"invalid-one\" does not exist, service \"invalid-two\" does not exist",
						Message: "service \"invalid-one\" does not exist, service \"invalid-two\" does not exist",
	rh, c, done := setup(t)
	rh, c, done := setup(t)
		f.CreateHTTPProxyAndWaitFor(p, httpProxyValid)
		f.CreateHTTPProxyAndWaitFor(p, httpProxyValid)
	f.NamespacedTest("018-external-name-service-insecure", testExternalNameServiceInsecure)
	f.NamespacedTest("018-external-name-service-tls", testExternalNameServiceTLS)
	return proxy != nil && proxy.Status.CurrentStatus == "valid"
Fixed code: 	log.Debugf("EnableExternalNameService is set to %t", ctx.Config.EnableExternalNameService)
			EnableExternalNameService: ctx.Config.EnableExternalNameService,
			FieldLogger:               log.WithField("context", "IngressProcessor"),
			ClientCertificate:         clientCert,
			// Note that ExtensionService does not support ExternalName, if it does get added,
			// need to bring EnableExternalNameService in here too.
			EnableExternalNameService: ctx.Config.EnableExternalNameService,
			DisablePermitInsecure:     ctx.Config.DisablePermitInsecure,
			FallbackCertificate:       fallbackCert,
			DNSLookupFamily:           ctx.Config.Cluster.DNSLookupFamily,
			ClientCertificate:         clientCert,
			RequestHeadersPolicy:      &requestHeadersPolicy,
			ResponseHeadersPolicy:     &responseHeadersPolicy,
			EnableExternalNameService: ctx.Config.EnableExternalNameService,
			FieldLogger:               log.WithField("context", "GatewayAPIProcessor"),
    ####
    # ExternalName Services are disabled by default due to CVE-2021-XXXXX
    # You can re-enable them by setting this setting to `true`.
    # This is not recommended without understanding the security implications.
    # Please see the advisory at https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc for the details.
    # enableExternalNameService: false
    ## 
    ####
    # ExternalName Services are disabled by default due to CVE-2021-XXXXX
    # You can re-enable them by setting this setting to `true`.
    # This is not recommended without understanding the security implications.
    # Please see the advisory at https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc for the details.
    # enableExternalNameService: false
    ##
    ####
    # ExternalName Services are disabled by default due to CVE-2021-XXXXX
    # You can re-enable them by setting this setting to `true`.
    # This is not recommended without understanding the security implications.
    # Please see the advisory at https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc for the details.
    # enableExternalNameService: false
    ##
	github.com/davecgh/go-spew v1.1.1 // indirect
func (dag *DAG) EnsureService(meta types.NamespacedName, port intstr.IntOrString, cache *KubernetesCache, enableExternalNameSvc bool) (*Service, error) {
	err = validateExternalName(svc, enableExternalNameSvc)
	if err != nil {
		return nil, err
	}

func validateExternalName(svc *v1.Service, enableExternalNameSvc bool) error {

	// If this isn't an ExternalName Service, we're all good here.
	en := externalName(svc)
	if en == "" {
		return nil
	}

	// If ExternalNames are disabled, then we don't want to add this to the DAG.
	if !enableExternalNameSvc {
		return fmt.Errorf("%s/%s is an ExternalName service, these are not currently enabled. See the config.enableExternalNameService config file setting", svc.Namespace, svc.Name)
	}

	// Check against a list of known localhost names, using a map to approximate a set.
	// TODO(youngnick) This is a very porous hack, and we should probably look into doing a DNS
	// lookup to check what the externalName resolves to, but I'm worried about the
	// performance impact of doing one or more DNS lookups per DAG run, so we're
	// going to go with a specific blocklist for now.
	localhostNames := map[string]struct{}{
		"localhost":               {},
		"localhost.localdomain":   {},
		"local.projectcontour.io": {},
	}

	_, localhost := localhostNames[en]
	if localhost {
		return fmt.Errorf("%s/%s is an ExternalName service that points to localhost, this is not allowed", svc.Namespace, svc.Name)
	}

	return nil
}


	externalNameValid := &v1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "externalnamevalid",
			Namespace: "default",
		},
		Spec: v1.ServiceSpec{
			Type:         v1.ServiceTypeExternalName,
			ExternalName: "external.projectcontour.io",
			Ports: []v1.ServicePort{{
				Name:       "http",
				Protocol:   "TCP",
				Port:       80,
				TargetPort: intstr.FromInt(80),
			}},
		},
	}

	externalNameLocalhost := &v1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "externalnamelocalhost",
			Namespace: "default",
		},
		Spec: v1.ServiceSpec{
			Type:         v1.ServiceTypeExternalName,
			ExternalName: "localhost",
			Ports: []v1.ServicePort{{
				Name:       "http",
				Protocol:   "TCP",
				Port:       80,
				TargetPort: intstr.FromInt(80),
			}},
		},
	}

		{Name: "service1", Namespace: "default"}:              s1,
		{Name: "externalnamevalid", Namespace: "default"}:     externalNameValid,
		{Name: "externalnamelocalhost", Namespace: "default"}: externalNameLocalhost,
		port                  intstr.IntOrString
		enableExternalNameSvc bool
		want                  *Service
		wantErr               error
		"When ExternalName Services are not disabled no error is returned": {
			NamespacedName: types.NamespacedName{Name: "externalnamevalid", Namespace: "default"},
			port:           intstr.FromString("80"),
			want: &Service{
				Weighted: WeightedService{
					Weight:           1,
					ServiceName:      "externalnamevalid",
					ServiceNamespace: "default",
					ServicePort: v1.ServicePort{
						Name:       "http",
						Protocol:   "TCP",
						Port:       80,
						TargetPort: intstr.FromInt(80),
					},
				},
				ExternalName: "external.projectcontour.io",
			},
			enableExternalNameSvc: true,
		},
		"When ExternalName Services are disabled an error is returned": {
			NamespacedName: types.NamespacedName{Name: "externalnamevalid", Namespace: "default"},
			port:           intstr.FromString("80"),
			wantErr:        errors.New(`default/externalnamevalid is an ExternalName service, these are not currently enabled. See the config.enableExternalNameService config file setting`),
		},
		"When ExternalName Services are enabled but a localhost ExternalName is used an error is returned": {
			NamespacedName:        types.NamespacedName{Name: "externalnamelocalhost", Namespace: "default"},
			port:                  intstr.FromString("80"),
			wantErr:               errors.New(`default/externalnamelocalhost is an ExternalName service that points to localhost, this is not allowed`),
			enableExternalNameSvc: true,
		},
			got, gotErr := dag.EnsureService(tc.NamespacedName, tc.port, &b.Source, tc.enableExternalNameSvc)
	ingressExternalNameService := &networking_v1.Ingress{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "externalname",
			Namespace: "default",
		},
		Spec: networking_v1.IngressSpec{
			Rules: []networking_v1.IngressRule{{
				Host: "example.com",
				IngressRuleValue: networking_v1.IngressRuleValue{
					HTTP: &networking_v1.HTTPIngressRuleValue{
						Paths: []networking_v1.HTTPIngressPath{{
							Backend: *backendv1(s14.GetName(), intstr.FromInt(80)),
						}},
					},
				},
			}},
		},
	}

		enableExternalNameSvc        bool
		"insert ingress with externalName service": {
			objs: []interface{}{
				ingressExternalNameService,
				s14,
			},
			enableExternalNameSvc: true,
			want: listeners(
				&Listener{
					Port: 80,
					VirtualHosts: virtualhosts(
						virtualhost("example.com", &Route{
							PathMatchCondition: prefixString("/"),
							Clusters: []*Cluster{{
								Upstream: &Service{
									ExternalName: "externalservice.io",
									Weighted: WeightedService{
										Weight:           1,
										ServiceName:      s14.Name,
										ServiceNamespace: s14.Namespace,
										ServicePort:      s14.Spec.Ports[0],
									},
								},
							}},
						}),
					),
				},
			),
		},
		"insert ingress with externalName service, but externalName services disabled": {
			objs: []interface{}{
				ingressExternalNameService,
				s14,
			},
			enableExternalNameSvc: false,
			want:                  listeners(),
		},
			enableExternalNameSvc: true,
			enableExternalNameSvc: true,
			enableExternalNameSvc: true,
			enableExternalNameSvc: true,
			want:                  listeners(),
						FieldLogger:               fixture.NewTestLogger(t),
						EnableExternalNameService: tc.enableExternalNameSvc,
						EnableExternalNameService: tc.enableExternalNameSvc,
						DisablePermitInsecure:     tc.disablePermitInsecure,
		// TODO(youngnick): If ExternalName support is added, we must pass down the EnableExternalNameService bool
		// and check it first.

	// EnableExternalNameService allows processing of ExternalNameServices
	// This is normally disabled for security reasons.
	// See https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc for details.
	EnableExternalNameService bool
	service, err := p.dag.EnsureService(meta, intstr.FromInt(int(*port)), p.source, p.EnableExternalNameService)
		return nil, fmt.Errorf("service %q is invalid: %s", meta.Name, err)
	// EnableExternalNameService allows processing of ExternalNameServices
	// This is normally disabled for security reasons.
	// See https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc for details.
	EnableExternalNameService bool

			s, err := p.dag.EnsureService(m, intstr.FromInt(service.Port), p.source, p.EnableExternalNameService)
			s, err := p.dag.EnsureService(m, intstr.FromInt(service.Port), p.source, p.EnableExternalNameService)
				validCond.AddErrorf(contour_api_v1.ConditionTypeTCPProxyError, "ServiceUnresolvedReference",

	// EnableExternalNameService allows processing of ExternalNameServices
	// This is normally disabled for security reasons.
	// See https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc for details.
	EnableExternalNameService bool
		s, err := p.dag.EnsureService(m, port, p.source, p.EnableExternalNameService)
				WithError(contour_api_v1.ConditionTypeTCPProxyError, "ServiceUnresolvedReference", `Spec.TCPProxy unresolved service reference: service "roots/not-found" not found`),
				WithError(contour_api_v1.ConditionTypeTCPProxyError, "ServiceUnresolvedReference", `Spec.TCPProxy unresolved service reference: port "9999" on service "roots/kuard" not matched`),
					Message: "service \"invalid-one\" is invalid: service \"default/invalid-one\" not found, service \"invalid-two\" is invalid: service \"default/invalid-two\" not found",
						Message: "service \"invalid-one\" is invalid: service \"default/invalid-one\" not found, service \"invalid-two\" is invalid: service \"default/invalid-two\" not found",
	"github.com/projectcontour/contour/internal/contour"
	"github.com/projectcontour/contour/internal/dag"
	"github.com/sirupsen/logrus"
	rh, c, done := setup(t, enableExternalNameService(t))

func enableExternalNameService(t *testing.T) func(eh *contour.EventHandler) {
	return func(eh *contour.EventHandler) {

		log := fixture.NewTestLogger(t)
		log.SetLevel(logrus.DebugLevel)

		eh.Builder.Processors = []dag.Processor{
			&dag.IngressProcessor{
				EnableExternalNameService: true,
				FieldLogger:               log.WithField("context", "IngressProcessor"),
			},
			&dag.HTTPProxyProcessor{
				EnableExternalNameService: true,
			},
			&dag.ExtensionServiceProcessor{
				FieldLogger: log.WithField("context", "ExtensionServiceProcessor"),
			},
			&dag.ListenerProcessor{},
		}
	}
}
	// Enable ExternalName processing here because
	// we need to check that host rewrites work in combination
	// with ExternalName.
	rh, c, done := setup(t, enableExternalNameService(t))
	// EnableExternalNameService allows processing of ExternalNameServices
	// Defaults to disabled for security reasons.
	// TODO(youngnick): put a link to the issue and CVE here.
	EnableExternalNameService bool `yaml:"enableExternalNameService,omitempty"`

| enableExternalNameService | boolean | `false` | Enable ExternalName Service processing. Enabling this has security implications. Please see the [advisory](https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc) for more details. |
		proxy, ok := f.CreateHTTPProxyAndWaitFor(p, httpProxyValid)
		if !ok {
			t.Fatalf("The HTTPProxy did not become valid, here are the Valid condition's Errors: %s", httpProxyErrors(proxy))
		}
		proxy, ok := f.CreateHTTPProxyAndWaitFor(p, httpProxyValid)
		if !ok {
			t.Fatalf("The HTTPProxy did not become valid, here are the Valid condition's Errors: %s", httpProxyErrors(proxy))
		}

func testExternalNameServiceLocalhostInvalid(namespace string) {
	Specify("external name services with localhost are rejected", func() {
		t := f.T()

		f.Fixtures.Echo.Deploy(namespace, "ingress-conformance-echo")

		externalNameService := &corev1.Service{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: namespace,
				Name:      "external-name-service-localhost",
			},
			Spec: corev1.ServiceSpec{
				Type: corev1.ServiceTypeExternalName,
				// The unit tests test just `localhost`, so test another item from that
				// list.
				ExternalName: "localhost.localdomain",
				Ports: []corev1.ServicePort{
					{
						Name: "http",
						Port: 80,
					},
				},
			},
		}
		require.NoError(t, f.Client.Create(context.TODO(), externalNameService))

		p := &contourv1.HTTPProxy{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: namespace,
				Name:      "external-name-proxy",
			},
			Spec: contourv1.HTTPProxySpec{
				VirtualHost: &contourv1.VirtualHost{
					Fqdn: "externalnameservice.projectcontour.io",
				},
				Routes: []contourv1.Route{
					{
						Services: []contourv1.Service{
							{
								Name: externalNameService.Name,
								Port: 80,
							},
						},
						RequestHeadersPolicy: &contourv1.HeadersPolicy{
							Set: []contourv1.HeaderValue{
								{
									Name:  "Host",
									Value: externalNameService.Spec.ExternalName,
								},
							},
						},
					},
				},
			},
		}
		_, ok := f.CreateHTTPProxyAndWaitFor(p, httpProxyValid)
		require.Falsef(t, ok, "ExternalName with hostname %s was accepted by Contour.", externalNameService.Spec.ExternalName)
	})
}
	"github.com/davecgh/go-spew/spew"
	f.NamespacedTest("018-external-name-service-insecure", func(namespace string) {
		Context("with ExternalName Services enabled", func() {
			BeforeEach(func() {
				contourConfig.EnableExternalNameService = true
			})
			testExternalNameServiceInsecure(namespace)
		})
	})
	f.NamespacedTest("018-external-name-service-tls", func(namespace string) {
		Context("with ExternalName Services enabled", func() {
			BeforeEach(func() {
				contourConfig.EnableExternalNameService = true
			})
			testExternalNameServiceTLS(namespace)
		})
	})
	f.NamespacedTest("018-external-name-service-localhost", func(namespace string) {
		Context("with ExternalName Services enabled", func() {
			BeforeEach(func() {
				contourConfig.EnableExternalNameService = true
			})
			testExternalNameServiceLocalhostInvalid(namespace)
		})
	})

	if proxy == nil {
		return false
	}

	if len(proxy.Status.Conditions) == 0 {
		return false
	}

	cond := proxy.Status.GetConditionFor("Valid")
	return cond.Status == "True"

}

// httpProxyErrors provides a pretty summary of any Errors on the HTTPProxy Valid condition.
// If there are no errors, the return value will be empty.
func httpProxyErrors(proxy *contourv1.HTTPProxy) string {
	cond := proxy.Status.GetConditionFor("Valid")
	errors := cond.Errors
	if len(errors) > 0 {
		return spew.Sdump(errors)
	}

	return ""