diff --git a/.changelog/9042.txt b/.changelog/9042.txt new file mode 100644 index 0000000000..3163296d2e --- /dev/null +++ b/.changelog/9042.txt @@ -0,0 +1,3 @@ +```release-note:improvement +connect: Automatically rewrite the Host header for Terminating Gateway HTTP services +``` diff --git a/agent/xds/routes.go b/agent/xds/routes.go index 6530b9fb4f..5b21badd9e 100644 --- a/agent/xds/routes.go +++ b/agent/xds/routes.go @@ -104,7 +104,7 @@ func (s *Server) routesFromSnapshotTerminatingGateway(_ connectionInfo, cfgSnap if resolver.LoadBalancer != nil { lb = resolver.LoadBalancer } - route, err := makeNamedDefaultRouteWithLB(clusterName, lb) + route, err := makeNamedDefaultRouteWithLB(clusterName, lb, true) if err != nil { logger.Error("failed to make route", "cluster", clusterName, "error", err) continue @@ -114,7 +114,7 @@ func (s *Server) routesFromSnapshotTerminatingGateway(_ connectionInfo, cfgSnap // If there is a service-resolver for this service then also setup routes for each subset for name := range resolver.Subsets { clusterName = connect.ServiceSNI(svc.Name, name, svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain) - route, err := makeNamedDefaultRouteWithLB(clusterName, lb) + route, err := makeNamedDefaultRouteWithLB(clusterName, lb, true) if err != nil { logger.Error("failed to make route", "cluster", clusterName, "error", err) continue @@ -126,13 +126,20 @@ func (s *Server) routesFromSnapshotTerminatingGateway(_ connectionInfo, cfgSnap return resources, nil } -func makeNamedDefaultRouteWithLB(clusterName string, lb *structs.LoadBalancer) (*envoy_route_v3.RouteConfiguration, error) { +func makeNamedDefaultRouteWithLB(clusterName string, lb *structs.LoadBalancer, autoHostRewrite bool) (*envoy_route_v3.RouteConfiguration, error) { action := makeRouteActionFromName(clusterName) if err := injectLBToRouteAction(lb, action.Route); err != nil { return nil, fmt.Errorf("failed to apply load balancer configuration to route action: %v", err) } + // Configure Envoy to rewrite Host header + if autoHostRewrite { + action.Route.HostRewriteSpecifier = &envoy_route_v3.RouteAction_AutoHostRewrite{ + AutoHostRewrite: makeBoolValue(true), + } + } + return &envoy_route_v3.RouteConfiguration{ Name: clusterName, VirtualHosts: []*envoy_route_v3.VirtualHost{ diff --git a/agent/xds/testdata/routes/terminating-gateway-lb-config.envoy-1-17-x.golden b/agent/xds/testdata/routes/terminating-gateway-lb-config.envoy-1-17-x.golden index 07f0248e2b..d228a0cdb5 100644 --- a/agent/xds/testdata/routes/terminating-gateway-lb-config.envoy-1-17-x.golden +++ b/agent/xds/testdata/routes/terminating-gateway-lb-config.envoy-1-17-x.golden @@ -16,6 +16,7 @@ "prefix": "/" }, "route": { + "autoHostRewrite": true, "cluster": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "hashPolicy": [ { @@ -58,6 +59,7 @@ "prefix": "/" }, "route": { + "autoHostRewrite": true, "cluster": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "hashPolicy": [ { @@ -100,6 +102,7 @@ "prefix": "/" }, "route": { + "autoHostRewrite": true, "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "hashPolicy": [ { @@ -130,4 +133,4 @@ ], "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", "nonce": "00000001" -} \ No newline at end of file +} diff --git a/agent/xds/testdata/routes/terminating-gateway-lb-config.v2compat.envoy-1-17-x.golden b/agent/xds/testdata/routes/terminating-gateway-lb-config.v2compat.envoy-1-17-x.golden index b611e2508d..940fac7bb7 100644 --- a/agent/xds/testdata/routes/terminating-gateway-lb-config.v2compat.envoy-1-17-x.golden +++ b/agent/xds/testdata/routes/terminating-gateway-lb-config.v2compat.envoy-1-17-x.golden @@ -16,6 +16,7 @@ "prefix": "/" }, "route": { + "autoHostRewrite": true, "cluster": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "hashPolicy": [ { @@ -58,6 +59,7 @@ "prefix": "/" }, "route": { + "autoHostRewrite": true, "cluster": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "hashPolicy": [ { @@ -100,6 +102,7 @@ "prefix": "/" }, "route": { + "autoHostRewrite": true, "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "hashPolicy": [ { @@ -130,4 +133,4 @@ ], "typeUrl": "type.googleapis.com/envoy.api.v2.RouteConfiguration", "nonce": "00000001" -} \ No newline at end of file +} diff --git a/test/integration/connect/envoy/case-terminating-gateway-hostnames/config_entries.hcl b/test/integration/connect/envoy/case-terminating-gateway-hostnames/config_entries.hcl index 54d471acc4..63e2106bf8 100644 --- a/test/integration/connect/envoy/case-terminating-gateway-hostnames/config_entries.hcl +++ b/test/integration/connect/envoy/case-terminating-gateway-hostnames/config_entries.hcl @@ -9,4 +9,9 @@ config_entries { } ] } + bootstrap { + kind = "service-defaults" + name = "s4" + protocol = "http" + } } diff --git a/test/integration/connect/envoy/case-terminating-gateway-hostnames/verify.bats b/test/integration/connect/envoy/case-terminating-gateway-hostnames/verify.bats index 6b4fba66b7..af76cb426b 100644 --- a/test/integration/connect/envoy/case-terminating-gateway-hostnames/verify.bats +++ b/test/integration/connect/envoy/case-terminating-gateway-hostnames/verify.bats @@ -31,3 +31,9 @@ load helpers @test "terminating-gateway is used for the upstream connection" { assert_envoy_metric_at_least 127.0.0.1:20000 "s4.default.primary.*cx_total" 1 } + +@test "terminating-gateway adds the Host header for connection to s3" { + # Envoy does not rewrite the port + # See https://github.com/envoyproxy/envoy/pull/504#discussion_r102614466 + assert_expected_fortio_host_header "localhost" localhost 5000 +} diff --git a/test/integration/connect/envoy/helpers.bash b/test/integration/connect/envoy/helpers.bash index bc03e6f40b..31f70e8430 100755 --- a/test/integration/connect/envoy/helpers.bash +++ b/test/integration/connect/envoy/helpers.bash @@ -848,3 +848,33 @@ function assert_expected_fortio_name_pattern { return 1 fi } + +function get_upstream_fortio_host_header { + local HOST=$1 + local PORT=$2 + local PREFIX=$3 + local DEBUG_HEADER_VALUE="${4:-""}" + local extra_args + if [[ -n "${DEBUG_HEADER_VALUE}" ]]; then + extra_args="-H x-test-debug:${DEBUG_HEADER_VALUE}" + fi + run retry_default curl -v -s -f -H"Host: ${HOST}" $extra_args \ + "localhost:${PORT}${PREFIX}/debug" + [ "$status" == 0 ] + echo "$output" | grep -E "^Host: " +} + +function assert_expected_fortio_host_header { + local EXPECT_HOST=$1 + local HOST=${2:-"localhost"} + local PORT=${3:-5000} + local URL_PREFIX=${4:-""} + local DEBUG_HEADER_VALUE="${5:-""}" + + GOT=$(get_upstream_fortio_host_header ${HOST} ${PORT} "${URL_PREFIX}" "${DEBUG_HEADER_VALUE}") + + if [ "$GOT" != "Host: ${EXPECT_HOST}" ]; then + echo "expected Host header: $EXPECT_HOST, actual Host header: $GOT" 1>&2 + return 1 + fi +}