diff --git a/internal/gatewayapi/backend.go b/internal/gatewayapi/backend.go index ec61e89ffc..bd5aedc67a 100644 --- a/internal/gatewayapi/backend.go +++ b/internal/gatewayapi/backend.go @@ -17,7 +17,6 @@ import ( egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" "github.com/envoyproxy/gateway/internal/gatewayapi/status" - "github.com/envoyproxy/gateway/internal/utils/net" ) func (t *Translator) ProcessBackends(backends []*egv1a1.Backend, backendTLSPolicies []*gwapiv1.BackendTLSPolicy) []*egv1a1.Backend { @@ -69,17 +68,9 @@ func validateBackend(backend *egv1a1.Backend, backendTLSPolicies []*gwapiv1.Back return routeErr } } else if ep.IP != nil { - ip, err := netip.ParseAddr(ep.IP.Address) - if err != nil { - return status.NewRouteStatusError( - fmt.Errorf("IP address %s is invalid", ep.IP.Address), - status.RouteReasonInvalidAddress, - ) - } else if ip.IsLoopback() && !runningOnHost { - return status.NewRouteStatusError( - fmt.Errorf("IP address %s in the loopback range is only supported when using the Host infrastructure", ep.IP.Address), - status.RouteReasonInvalidAddress, - ) + routeErr := validateIP(ep.IP, runningOnHost) + if routeErr != nil { + return routeErr } } } @@ -170,7 +161,7 @@ func validateBackendTLSSettings(backend *egv1a1.Backend, backendTLSPolicies []*g return nil } -func validateHostname(hostname, typeName string, allowLocalhost bool) *status.RouteStatusError { +func validateHostname(hostname, typeName string, runningOnHost bool) *status.RouteStatusError { // must be a valid hostname if errs := validation.IsDNS1123Subdomain(hostname); errs != nil { return status.NewRouteStatusError( @@ -178,8 +169,9 @@ func validateHostname(hostname, typeName string, allowLocalhost bool) *status.Ro status.RouteReasonInvalidAddress, ) } - isLocalHostname := allowLocalhost && hostname == net.DefaultLocalAddress - if !isLocalHostname && len(strings.Split(hostname, ".")) < 2 { + // Host mode is a dev configuration, so we do not enforce domain rules. + // Doing so would interfere with docker hostnames (e.g. "jaeger") or literal IPs. + if !runningOnHost && len(strings.Split(hostname, ".")) < 2 { return status.NewRouteStatusError( fmt.Errorf("hostname %s should be a domain with at least two segments separated by dots", hostname), status.RouteReasonInvalidAddress, @@ -195,3 +187,19 @@ func validateHostname(hostname, typeName string, allowLocalhost bool) *status.Ro return nil } + +func validateIP(epIP *egv1a1.IPEndpoint, runningOnHost bool) status.Error { + ip, err := netip.ParseAddr(epIP.Address) + if err != nil { + return status.NewRouteStatusError( + fmt.Errorf("IP address %s is invalid", epIP.Address), + status.RouteReasonInvalidAddress, + ) + } else if ip.IsLoopback() && !runningOnHost { + return status.NewRouteStatusError( + fmt.Errorf("IP address %s in the loopback range is only supported when using the Host infrastructure", epIP.Address), + status.RouteReasonInvalidAddress, + ) + } + return nil +} diff --git a/internal/gatewayapi/backend_test.go b/internal/gatewayapi/backend_test.go new file mode 100644 index 0000000000..3f81402be0 --- /dev/null +++ b/internal/gatewayapi/backend_test.go @@ -0,0 +1,100 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package gatewayapi + +import ( + "testing" + + "github.com/stretchr/testify/require" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" +) + +func TestValidateHostnameRunningOnHost(t *testing.T) { + cases := []struct { + name string + hostname string + runningOnHost bool + expectedErr string + }{ + { + name: "domain ok in any case", + hostname: "httpbin.org", + runningOnHost: false, + }, + { + name: "single label not ok when in k8s", + hostname: "otel-tui", + runningOnHost: false, + expectedErr: "hostname otel-tui should be a domain with at least two segments separated by dots", + }, + { + name: "single label ok when running on host", + hostname: "otel-tui", + runningOnHost: true, + }, + { + name: "IP not ok in any case", + hostname: "127.0.0.1", + runningOnHost: true, + expectedErr: "hostname 127.0.0.1 is an IP address", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := validateHostname(tc.hostname, "hostname", tc.runningOnHost) + if tc.expectedErr == "" { + require.Nil(t, err) + return + } + require.EqualError(t, err, tc.expectedErr) + }) + } +} + +func TestValidateIPRunningOnHost(t *testing.T) { + cases := []struct { + name string + address string + runningOnHost bool + expectedErr string + }{ + { + name: "address ok in any case", + address: "10.0.0.1", + runningOnHost: false, + }, + { + name: "loopback not ok when in k8s", + address: "127.0.0.1", + runningOnHost: false, + expectedErr: "IP address 127.0.0.1 in the loopback range is only supported when using the Host infrastructure", + }, + { + name: "loopback ok when running on host", + address: "127.0.0.1", + runningOnHost: true, + }, + { + name: "invalid IP not ok in any case", + address: "300.0.0.1", + runningOnHost: true, + expectedErr: "IP address 300.0.0.1 is invalid", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := validateIP(&egv1a1.IPEndpoint{Address: tc.address}, tc.runningOnHost) + if tc.expectedErr == "" { + require.Nil(t, err) + return + } + require.EqualError(t, err, tc.expectedErr) + }) + } +} diff --git a/release-notes/current.yaml b/release-notes/current.yaml index 3271a4f907..792dc71653 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -10,6 +10,7 @@ security updates: | new features: | bug fixes: | + Allowed single-label backend hostnames when running with the Host infrastructure, enabling Docker Compose service names for telemetry backends. # Enhancements that improve performance. performance improvements: |