Skipper Ingress Controller Allows Unauthorized Access to Internal Services via ExternalName
Description
Skipper is an HTTP router and reverse proxy for service composition. Prior to version 0.24.0, when running Skipper as an Ingress controller, users with permissions to create an Ingress and a Service of type ExternalName can create routes that enable them to use Skipper's network access to reach internal services. Version 0.24.0 disables Kubernetes ExternalName by default. As a workaround, developers can allow list targets of an ExternalName and allow list via regular expressions.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/zalando/skipperGo | < 0.24.0 | 0.24.0 |
Affected products
1Patches
1a4c87ce029a5security: disable kubernetes external name by default (#3842)
17 files changed · +109 −10
config/config.go+3 −0 modified@@ -190,6 +190,7 @@ type Config struct { KubernetesAnnotationPredicates []kubernetes.AnnotationPredicates `yaml:"-"` KubernetesAnnotationFiltersAppend []kubernetes.AnnotationFilters `yaml:"-"` KubernetesEastWestRangePredicates []*eskip.Predicate `yaml:"-"` + EnableKubernetesExternalNames bool `yaml:"enable-kubernetes-external-names"` KubernetesOnlyAllowedExternalNames bool `yaml:"kubernetes-only-allowed-external-names"` KubernetesAllowedExternalNames regexpListFlag `yaml:"kubernetes-allowed-external-names"` KubernetesRedisServiceNamespace string `yaml:"kubernetes-redis-service-namespace"` @@ -522,6 +523,7 @@ func NewConfig() *Config { flag.Var(&cfg.KubernetesAnnotationFiltersAppendString, "kubernetes-annotation-filters-append", "configures filters appended to non east-west routes of annotated resources. E.g. -kubernetes-annotation-filters-append='zone-a=true=foo() -> bar()' will add 'foo() -> bar()' filters to all non east-west routes of ingress or routegroup annotated with 'zone-a: true'. For east-west routes use -kubernetes-east-west-range-annotation-filters-append.") flag.Var(&cfg.KubernetesEastWestRangeAnnotationPredicatesString, "kubernetes-east-west-range-annotation-predicates", "similar to -kubernetes-annotation-predicates configures predicates appended to east-west routes of annotated resources. See also -kubernetes-east-west-range-domains.") flag.Var(&cfg.KubernetesEastWestRangeAnnotationFiltersAppendString, "kubernetes-east-west-range-annotation-filters-append", "similar to -kubernetes-annotation-filters-append configures filters appended to east-west routes of annotated resources. See also -kubernetes-east-west-range-domains.") + flag.BoolVar(&cfg.EnableKubernetesExternalNames, "enable-kubernetes-external-names", false, "only if enabled we allow to use external name services as backends in Ingress") flag.BoolVar(&cfg.KubernetesOnlyAllowedExternalNames, "kubernetes-only-allowed-external-names", false, "only accept external name services, route group network backends and route group explicit LB endpoints from an allow list defined by zero or more -kubernetes-allowed-external-name flags") flag.Var(&cfg.KubernetesAllowedExternalNames, "kubernetes-allowed-external-name", "set zero or more regular expressions from which at least one should be matched by the external name services, route group network addresses and explicit endpoints domain names") flag.StringVar(&cfg.KubernetesRedisServiceNamespace, "kubernetes-redis-service-namespace", "", "Sets namespace for redis to be used to lookup endpoints") @@ -948,6 +950,7 @@ func (c *Config) ToOptions() skipper.Options { KubernetesEastWestRangeAnnotationFiltersAppend: c.KubernetesEastWestRangeAnnotationFiltersAppend, KubernetesAnnotationPredicates: c.KubernetesAnnotationPredicates, KubernetesAnnotationFiltersAppend: c.KubernetesAnnotationFiltersAppend, + EnableKubernetesExternalNames: c.EnableKubernetesExternalNames, KubernetesOnlyAllowedExternalNames: c.KubernetesOnlyAllowedExternalNames, KubernetesAllowedExternalNames: c.KubernetesAllowedExternalNames, KubernetesRedisServiceNamespace: c.KubernetesRedisServiceNamespace,
dataclients/kubernetes/ingress.go+8 −2 modified@@ -37,6 +37,7 @@ type ingressContext struct { annotationPredicate string annotationBackend string forwardBackendURL string + enableExternalNames bool extraRoutes []*eskip.Route backendWeights map[string]float64 pathMode PathMode @@ -58,6 +59,7 @@ type ingress struct { provideHTTPSRedirect bool disableCatchAllRoutes bool forceKubernetesService bool + enableExternalNames bool backendTrafficAlgorithm BackendTrafficAlgorithm defaultLoadBalancerAlgorithm string forwardBackendURL string @@ -67,9 +69,12 @@ type ingress struct { kubernetesEastWestRangeAnnotationFiltersAppend []AnnotationFilters } -var nonWord = regexp.MustCompile(`\W`) +var ( + nonWord = regexp.MustCompile(`\W`) -var errNotAllowedExternalName = errors.New("ingress with not allowed external name service") + errNotEnabledExternalName = errors.New("ingress is not enabled to reference external name service") + errNotAllowedExternalName = errors.New("ingress with not allowed external name service") +) func (ic *ingressContext) addHostRoute(host string, route *eskip.Route) { ic.applyBackend(route) @@ -102,6 +107,7 @@ func newIngress(o Options) *ingress { kubernetesEastWestDomain: o.KubernetesEastWestDomain, eastWestRangeDomains: o.KubernetesEastWestRangeDomains, eastWestRangePredicates: o.KubernetesEastWestRangePredicates, + enableExternalNames: o.EnableExternalNames, allowedExternalNames: o.AllowedExternalNames, forceKubernetesService: o.ForceKubernetesService, backendTrafficAlgorithm: o.BackendTrafficAlgorithm,
dataclients/kubernetes/ingressv1.go+16 −3 modified@@ -109,7 +109,10 @@ func convertPathRuleV1( ic.logger.Errorf("Failed to find target port for service %s, but %d endpoints exist", svcName, len(eps)) } } else if svc.Spec.Type == "ExternalName" { - return externalNameRoute(ns, name, host, hostRegexp, svc, servicePort, allowedExternalNames) + if ic.enableExternalNames { + return externalNameRoute(ns, name, host, hostRegexp, svc, servicePort, allowedExternalNames) + } + return nil, errNotEnabledExternalName } else if forceKubernetesService { eps = []string{serviceNameBackend(svcName, ns, servicePort)} } else { @@ -185,6 +188,11 @@ func (ing *ingress) addEndpointsRuleV1(ic *ingressContext, host string, prule *d return nil } + if errors.Is(err, errNotEnabledExternalName) { + ic.logger.Infof("Not enabled to reference external name from ingress: %s/%s", meta.Namespace, meta.Name) + return nil + } + // Ingress status field does not support errors return fmt.Errorf("error while getting service: %w", err) } @@ -359,8 +367,12 @@ func (ing *ingress) convertDefaultBackendV1( ic.logger.Errorf("Failed to find target port %v, %s, for service %s add shuntroute: %v", svc.Spec.Ports, svcPort, svcName, err) err = nil } else if svc.Spec.Type == "ExternalName" { - r, err := externalNameRoute(ns, name, "default", nil, svc, servicePort, ing.allowedExternalNames) - return r, err == nil, err + if ic.enableExternalNames { + r, err := externalNameRoute(ns, name, "default", nil, svc, servicePort, ing.allowedExternalNames) + return r, err == nil, err + } + return nil, false, errNotEnabledExternalName + } else if forceKubernetesService { eps = []string{serviceNameBackend(svcName, ns, servicePort)} } else { @@ -435,6 +447,7 @@ func (ing *ingress) ingressV1Route( annotationPredicate: annotationPredicate(i.Metadata), annotationBackend: annotationBackendString(i.Metadata), forwardBackendURL: ing.forwardBackendURL, + enableExternalNames: ing.enableExternalNames, extraRoutes: extraRoutes(i.Metadata), backendWeights: backendWeights(i.Metadata, logger), pathMode: pathMode(i.Metadata, ing.pathMode, logger),
dataclients/kubernetes/kube.go+4 −0 modified@@ -233,6 +233,10 @@ type Options struct { // (using tracingTag filter) should be added to all routes BackendNameTracingTag bool + // EnableExternalNames enables the integration of Kubernetes + // Service type ExternalName as backends in Ingress. + EnableExternalNames bool + // OnlyAllowedExternalNames will enable validation of ingress external names and route groups network // backend addresses, explicit LB endpoints validation against the list of patterns in // AllowedExternalNames.
dataclients/kubernetes/kubernetestest/fixtures.go+2 −0 modified@@ -41,6 +41,7 @@ type kubeOptionsParser struct { HTTPSRedirectCode int `yaml:"httpsRedirectCode"` DisableCatchAllRoutes bool `yaml:"disableCatchAllRoutes"` BackendNameTracingTag bool `yaml:"backendNameTracingTag"` + EnableExternalNames bool `yaml:"enableExternalNames"` OnlyAllowedExternalNames bool `yaml:"onlyAllowedExternalNames"` AllowedExternalNames []string `yaml:"allowedExternalNames"` IngressClass string `yaml:"kubernetes-ingress-class"` @@ -263,6 +264,7 @@ func testFixture(t *testing.T, f fixtureSet) { t.Fatal(err) } + o.EnableExternalNames = kop.EnableExternalNames o.OnlyAllowedExternalNames = kop.OnlyAllowedExternalNames o.AllowedExternalNames = aen }
dataclients/kubernetes/testdata/ingressV1/external-name/all-allowed.kube+1 −0 modified@@ -1,3 +1,4 @@ +enableExternalNames: true onlyAllowedExternalNames: true allowedExternalNames: - ^external1[.]example[.]org$
dataclients/kubernetes/testdata/ingressV1/external-name/external-name-validation-not-enabled.kube+1 −0 added@@ -0,0 +1 @@ +enableExternalNames: true
dataclients/kubernetes/testdata/ingressV1/external-name/none-allowed.kube+1 −0 modified@@ -1 +1,2 @@ +enableExternalNames: true onlyAllowedExternalNames: true
dataclients/kubernetes/testdata/ingressV1/external-name/not-allowed.eskip+0 −0 addeddataclients/kubernetes/testdata/ingressV1/external-name/not-allowed.kube+5 −0 added@@ -0,0 +1,5 @@ +enableExternalNames: false +onlyAllowedExternalNames: true +allowedExternalNames: +- ^external1[.]example[.]org$ +- ^external2[.]example[.]org$
dataclients/kubernetes/testdata/ingressV1/external-name/not-allowed.yaml+54 −0 added@@ -0,0 +1,54 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: myapp + namespace: default +spec: + rules: + - host: example.org + http: + paths: + - path: /one + pathType: ImplementationSpecific + backend: + service: + name: external1 + port: + name: ext + - path: /two + pathType: ImplementationSpecific + backend: + service: + name: external2 + port: + name: ext +--- +apiVersion: v1 +kind: Service +metadata: + labels: + application: myapp + name: external1 +spec: + type: ExternalName + externalName: external1.example.org + ports: + - name: ext + port: 443 + protocol: TCP + targetPort: 443 +--- +apiVersion: v1 +kind: Service +metadata: + labels: + application: myapp + name: external2 +spec: + type: ExternalName + externalName: external2.example.org + ports: + - name: ext + port: 443 + protocol: TCP + targetPort: 443
dataclients/kubernetes/testdata/ingressV1/external-name/some-allowed.kube+1 −0 modified@@ -1,3 +1,4 @@ +enableExternalNames: true onlyAllowedExternalNames: true allowedExternalNames: - ^external1[.]example[.]org$
dataclients/kubernetes/testdata/ingressV1/ingress-data/ing-with-externalname-svc.kube+2 −0 added@@ -0,0 +1,2 @@ +enableExternalNames: true +
docs/kubernetes/external-addresses.md+5 −3 modified@@ -1,15 +1,15 @@ # External Addresses (External Name) -In Kubernetes, it is possible to define services with external names (type=ExternalName). For ingress objects, -Skipper supports these services, and generates routes from the ingress objects that reference one or more +In Kubernetes, it is possible to define services with external names (type=ExternalName). For Ingress objects, +Skipper supports these services, if enabled by `-enable-kubernetes-external-names`. Skipper generates routes from the Ingress objects that reference one or more external name service, that will have a backend pointing to the network address defined by the specified service. Route groups don't support services of type ExternalName, but they support network backends, and even LB backends with explicit endpoints with custom endpoint addresses. This way, it is possible to achieve the same with route groups. -For both the ingress objects and the route groups, the accepted external addresses must be explicitly allowed by +For both the Ingress objects and the route groups, the accepted external addresses must be explicitly allowed by listing regexp expressions of which at least one must be matched by the domain name of these addresses. The allow list is a startup option, defined via command line flags or in the configuration file. Enforcing this list happens only in the Kubernetes Ingress mode of Skipper. @@ -20,6 +20,7 @@ For compatibility reasons, the validation needs to be enabled with an explicit t ```sh skipper -kubernetes \ +-enable-kubernetes-external-names \ -kubernetes-only-allowed-external-names \ -kubernetes-allowed-external-name "^one[.]example[.]org$" \ -kubernetes-allowed-external-name "^two[.]example[.]org$" @@ -30,6 +31,7 @@ skipper -kubernetes \ For compatibility reasons, the validation needs to be enabled with an explicit toggle: ```yaml +enable-kubernetes-external-names: true kubernetes-only-allowed-external-names: true kubernetes-allowed-external-names: - ^one[.]example[.]org$
docs/kubernetes/ingress-usage.md+1 −1 modified@@ -37,7 +37,7 @@ Service type | supported | workaround --- | --- | --- ClusterIP | yes | --- NodePort | yes | --- -ExternalName | yes | --- +ExternalName | yes (enable by `-enable-kubernetes-external-names` | --- LoadBalancer | no | it should not, because Kubernetes cloud-controller-manager will maintain it
skipper.go+4 −0 modified@@ -293,6 +293,9 @@ type Options struct { // KubernetesAnnotationFiltersAppend sets filters to append for each annotation key and value KubernetesAnnotationFiltersAppend []kubernetes.AnnotationFilters + // EnableKubernetesExternalNames enables to use Kubernetes service type ExternalName as backend in Ingress and RouteGroup. + EnableKubernetesExternalNames bool + // KubernetesOnlyAllowedExternalNames will enable validation of ingress external names and route groups network // backend addresses, explicit LB endpoints validation against the list of patterns in // AllowedExternalNames. @@ -1034,6 +1037,7 @@ type Options struct { func (o *Options) KubernetesDataClientOptions() kubernetes.Options { return kubernetes.Options{ AllowedExternalNames: o.KubernetesAllowedExternalNames, + EnableExternalNames: o.EnableKubernetesExternalNames, BackendNameTracingTag: o.OpenTracingBackendNameTag, DefaultFiltersDir: o.DefaultFiltersDir, KubernetesInCluster: o.KubernetesInCluster,
VERSION+1 −1 modified@@ -1 +1 @@ -v0.23 +v0.24
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-mxxc-p822-2hx9ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-24470ghsaADVISORY
- github.com/zalando/skipper/commit/a4c87ce029a58eb8e1c2c1f93049194a39cf6219ghsax_refsource_MISCWEB
- github.com/zalando/skipper/releases/tag/v0.24.0ghsaWEB
- github.com/zalando/skipper/security/advisories/GHSA-mxxc-p822-2hx9ghsax_refsource_CONFIRMWEB
- kubernetes.io/docs/concepts/services-networking/service/ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.