diff --git a/api/v1alpha1/httproutefilter_types.go b/api/v1alpha1/httproutefilter_types.go index 9ae8be59842..3259fabc8f4 100644 --- a/api/v1alpha1/httproutefilter_types.go +++ b/api/v1alpha1/httproutefilter_types.go @@ -43,7 +43,6 @@ type HTTPURLRewriteFilter struct { // forwarding. // // +optional - // +notImplementedHide Hostname *HTTPHostnameModifier `json:"hostname,omitempty"` // Path defines a path rewrite. // @@ -83,12 +82,12 @@ const ( type HTTPHostnameModifierType string const ( - // HeaderHTTPHostnameModifier indicates that the Host header value would be replaced with the value of the header specified in setFromHeader. + // HeaderHTTPHostnameModifier indicates that the Host header value would be replaced with the value of the header specified in header. // https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-host-rewrite-header - HeaderHTTPHostnameModifier HTTPHostnameModifierType = "SetFromHeader" + HeaderHTTPHostnameModifier HTTPHostnameModifierType = "Header" // BackendHTTPHostnameModifier indicates that the Host header value would be replaced by the DNS name of the backend if it exists. // https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-auto-host-rewrite - BackendHTTPHostnameModifier HTTPHostnameModifierType = "SetFromBackend" + BackendHTTPHostnameModifier HTTPHostnameModifierType = "Backend" ) type ReplaceRegexMatch struct { @@ -129,16 +128,16 @@ type HTTPPathModifier struct { ReplaceRegexMatch *ReplaceRegexMatch `json:"replaceRegexMatch,omitempty"` } -// +kubebuilder:validation:XValidation:message="setFromHeader must be nil if the type is not SetFromHeader",rule="!(has(self.setFromHeader) && self.type != 'SetFromHeader')" -// +kubebuilder:validation:XValidation:message="setFromHeader must be specified for SetFromHeader type",rule="!(!has(self.setFromHeader) && self.type == 'SetFromHeader')" +// +kubebuilder:validation:XValidation:message="header must be nil if the type is not Header",rule="!(has(self.header) && self.type != 'Header')" +// +kubebuilder:validation:XValidation:message="header must be specified for Header type",rule="!(!has(self.header) && self.type == 'Header')" type HTTPHostnameModifier struct { - // +kubebuilder:validation:Enum=SetFromHeader;SetFromBackend + // +kubebuilder:validation:Enum=Header;Backend // +kubebuilder:validation:Required Type HTTPHostnameModifierType `json:"type"` - // SetFromHeader is the name of the header whose value would be used to rewrite the Host header + // Header is the name of the header whose value would be used to rewrite the Host header // +optional - SetFromHeader *string `json:"setFromHeader,omitempty"` + Header *string `json:"header,omitempty"` } //+kubebuilder:object:root=true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index c225d65d39e..f2cf9072fa6 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -2747,8 +2747,8 @@ func (in *HTTPExtAuthService) DeepCopy() *HTTPExtAuthService { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPHostnameModifier) DeepCopyInto(out *HTTPHostnameModifier) { *out = *in - if in.SetFromHeader != nil { - in, out := &in.SetFromHeader, &out.SetFromHeader + if in.Header != nil { + in, out := &in.Header, &out.Header *out = new(string) **out = **in } diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_httproutefilters.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_httproutefilters.yaml index 672cfb59df8..195bf24ece8 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_httproutefilters.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_httproutefilters.yaml @@ -137,25 +137,25 @@ spec: Hostname is the value to be used to replace the Host header value during forwarding. properties: - setFromHeader: - description: SetFromHeader is the name of the header whose - value would be used to rewrite the Host header + header: + description: Header is the name of the header whose value + would be used to rewrite the Host header type: string type: description: HTTPPathModifierType defines the type of Hostname rewrite. enum: - - SetFromHeader - - SetFromBackend + - Header + - Backend type: string required: - type type: object x-kubernetes-validations: - - message: setFromHeader must be nil if the type is not SetFromHeader - rule: '!(has(self.setFromHeader) && self.type != ''SetFromHeader'')' - - message: setFromHeader must be specified for SetFromHeader type - rule: '!(!has(self.setFromHeader) && self.type == ''SetFromHeader'')' + - message: header must be nil if the type is not Header + rule: '!(has(self.header) && self.type != ''Header'')' + - message: header must be specified for Header type + rule: '!(!has(self.header) && self.type == ''Header'')' path: description: Path defines a path rewrite. properties: diff --git a/internal/gatewayapi/filters.go b/internal/gatewayapi/filters.go index 41acc4c76ba..e969b7365fc 100644 --- a/internal/gatewayapi/filters.go +++ b/internal/gatewayapi/filters.go @@ -11,6 +11,7 @@ import ( "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" @@ -147,14 +148,46 @@ func (t *Translator) ProcessGRPCFilters(parentRef *RouteParentContext, return httpFiltersContext } +// Checks if the context and the rewrite both contain a core gw-api HTTP URL rewrite +func hasMultipleCoreRewrites(rewrite *gwapiv1.HTTPURLRewriteFilter, contextRewrite *ir.URLRewrite) bool { + contextHasCoreRewrites := contextRewrite.Path != nil && (contextRewrite.Path.FullReplace != nil || + contextRewrite.Path.PrefixMatchReplace != nil) || (contextRewrite.Host != nil && contextRewrite.Host.Name != nil) + rewriteHasCoreRewrites := rewrite.Hostname != nil || rewrite.Path != nil + return contextHasCoreRewrites && rewriteHasCoreRewrites +} + +// Checks if the context and the rewrite both contain a envoy-gateway extended HTTP URL rewrite +func hasMultipleExtensionRewrites(rewrite *egv1a1.HTTPURLRewriteFilter, contextRewrite *ir.URLRewrite) bool { + contextHasExtensionRewrites := (contextRewrite.Path != nil && contextRewrite.Path.RegexMatchReplace != nil) || + (contextRewrite.Host != nil && (contextRewrite.Host.Header != nil || contextRewrite.Host.Backend != nil)) + + return contextHasExtensionRewrites && (rewrite.Hostname != nil || rewrite.Path != nil) +} + +// Checks if the context and the gw-api core rewrite both contain an HTTP URL rewrite that creates a conflict (e.g. both rewrite path) +func hasConflictingCoreAndExtensionRewrites(rewrite *gwapiv1.HTTPURLRewriteFilter, contextRewrite *ir.URLRewrite) bool { + contextHasExtensionPathRewrites := contextRewrite.Path != nil && contextRewrite.Path.RegexMatchReplace != nil + contextHasExtensionHostRewrites := contextRewrite.Host != nil && (contextRewrite.Host.Header != nil || + contextRewrite.Host.Backend != nil) + return (rewrite.Hostname != nil && contextHasExtensionHostRewrites) || (rewrite.Path != nil && contextHasExtensionPathRewrites) +} + +// Checks if the context and the envoy-gateway extended rewrite both contain an HTTP URL rewrite that creates a conflict (e.g. both rewrite path) +func hasConflictingExtensionAndCoreRewrites(rewrite *egv1a1.HTTPURLRewriteFilter, contextRewrite *ir.URLRewrite) bool { + contextHasCorePathRewrites := contextRewrite.Path != nil && (contextRewrite.Path.FullReplace != nil || + contextRewrite.Path.PrefixMatchReplace != nil) + contextHasCoreHostnameRewrites := contextRewrite.Host != nil && contextRewrite.Host.Name != nil + + return (rewrite.Hostname != nil && contextHasCoreHostnameRewrites) || (rewrite.Path != nil && contextHasCorePathRewrites) +} + func (t *Translator) processURLRewriteFilter( rewrite *gwapiv1.HTTPURLRewriteFilter, filterContext *HTTPFiltersContext, ) { if filterContext.URLRewrite != nil { - if filterContext.URLRewrite.Hostname != nil || - filterContext.URLRewrite.Path.FullReplace != nil || - filterContext.URLRewrite.Path.PrefixMatchReplace != nil { + if hasMultipleCoreRewrites(rewrite, filterContext.URLRewrite) || + hasConflictingCoreAndExtensionRewrites(rewrite, filterContext.URLRewrite) { routeStatus := GetRouteStatus(filterContext.Route) status.SetRouteStatusCondition(routeStatus, filterContext.ParentRef.routeParentStatusIdx, @@ -188,7 +221,9 @@ func (t *Translator) processURLRewriteFilter( return } redirectHost := string(*rewrite.Hostname) - newURLRewrite.Hostname = &redirectHost + newURLRewrite.Host = &ir.HTTPHostModifier{ + Name: &redirectHost, + } } if rewrite.Path != nil { @@ -751,48 +786,12 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec if string(extFilter.Kind) == egv1a1.KindHTTPRouteFilter { for _, hrf := range resources.HTTPRouteFilters { - if hrf.Namespace == filterNs && hrf.Name == string(extFilter.Name) && - hrf.Spec.URLRewrite.Path.Type == egv1a1.RegexHTTPPathModifier { - - if hrf.Spec.URLRewrite.Path.ReplaceRegexMatch == nil || - hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Pattern == "" { - errMsg := "ReplaceRegexMatch Pattern must be set when rewrite path type is \"ReplaceRegexMatch\"" - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - errMsg, - ) - return - } else if _, err := regexp.Compile(hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Pattern); err != nil { - // Avoid envoy NACKs due to invalid regex. - // Golang's regexp is almost identical to RE2: https://pkg.go.dev/regexp/syntax - errMsg := "ReplaceRegexMatch must be a valid RE2 regular expression" - routeStatus := GetRouteStatus(filterContext.Route) - status.SetRouteStatusCondition(routeStatus, - filterContext.ParentRef.routeParentStatusIdx, - filterContext.Route.GetGeneration(), - gwapiv1.RouteConditionAccepted, - metav1.ConditionFalse, - gwapiv1.RouteReasonUnsupportedValue, - errMsg, - ) - return - } - - rmr := &ir.RegexMatchReplace{ - Pattern: hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Pattern, - Substitution: hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Substitution, - } + if hrf.Namespace == filterNs && hrf.Name == string(extFilter.Name) { + if hrf.Spec.URLRewrite != nil { - if filterContext.HTTPFilterIR.URLRewrite != nil { - // If path IR is already set - check for a conflict - if filterContext.HTTPFilterIR.URLRewrite.Path != nil { - path := filterContext.HTTPFilterIR.URLRewrite.Path - if path.RegexMatchReplace != nil || path.PrefixMatchReplace != nil || path.FullReplace != nil { + if filterContext.URLRewrite != nil { + if hasMultipleExtensionRewrites(hrf.Spec.URLRewrite, filterContext.URLRewrite) || + hasConflictingExtensionAndCoreRewrites(hrf.Spec.URLRewrite, filterContext.URLRewrite) { routeStatus := GetRouteStatus(filterContext.Route) status.SetRouteStatusCondition(routeStatus, filterContext.ParentRef.routeParentStatusIdx, @@ -804,19 +803,100 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec ) return } - } else { // no path - filterContext.HTTPFilterIR.URLRewrite.Path = &ir.ExtendedHTTPPathModifier{ - RegexMatchReplace: rmr, + } + + if hrf.Spec.URLRewrite.Path != nil { + if hrf.Spec.URLRewrite.Path.Type == egv1a1.RegexHTTPPathModifier { + if hrf.Spec.URLRewrite.Path.ReplaceRegexMatch == nil || + hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Pattern == "" { + errMsg := "ReplaceRegexMatch Pattern must be set when rewrite path type is \"ReplaceRegexMatch\"" + routeStatus := GetRouteStatus(filterContext.Route) + status.SetRouteStatusCondition(routeStatus, + filterContext.ParentRef.routeParentStatusIdx, + filterContext.Route.GetGeneration(), + gwapiv1.RouteConditionAccepted, + metav1.ConditionFalse, + gwapiv1.RouteReasonUnsupportedValue, + errMsg, + ) + return + } else if _, err := regexp.Compile(hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Pattern); err != nil { + // Avoid envoy NACKs due to invalid regex. + // Golang's regexp is almost identical to RE2: https://pkg.go.dev/regexp/syntax + errMsg := "ReplaceRegexMatch must be a valid RE2 regular expression" + routeStatus := GetRouteStatus(filterContext.Route) + status.SetRouteStatusCondition(routeStatus, + filterContext.ParentRef.routeParentStatusIdx, + filterContext.Route.GetGeneration(), + gwapiv1.RouteConditionAccepted, + metav1.ConditionFalse, + gwapiv1.RouteReasonUnsupportedValue, + errMsg, + ) + return + } + + rmr := &ir.RegexMatchReplace{ + Pattern: hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Pattern, + Substitution: hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Substitution, + } + + if filterContext.HTTPFilterIR.URLRewrite != nil { + if filterContext.HTTPFilterIR.URLRewrite.Path == nil { + filterContext.HTTPFilterIR.URLRewrite.Path = &ir.ExtendedHTTPPathModifier{ + RegexMatchReplace: rmr, + } + return + } + } else { // no url rewrite + filterContext.HTTPFilterIR.URLRewrite = &ir.URLRewrite{ + Path: &ir.ExtendedHTTPPathModifier{ + RegexMatchReplace: rmr, + }, + } + return + } } - return } - } else { // no url rewrite - filterContext.HTTPFilterIR.URLRewrite = &ir.URLRewrite{ - Path: &ir.ExtendedHTTPPathModifier{ - RegexMatchReplace: rmr, - }, + + if hrf.Spec.URLRewrite.Hostname != nil { + var hm *ir.HTTPHostModifier + if hrf.Spec.URLRewrite.Hostname.Type == egv1a1.HeaderHTTPHostnameModifier { + if hrf.Spec.URLRewrite.Hostname.Header == nil { + errMsg := "Header must be set when rewrite path type is \"Header\"" + routeStatus := GetRouteStatus(filterContext.Route) + status.SetRouteStatusCondition(routeStatus, + filterContext.ParentRef.routeParentStatusIdx, + filterContext.Route.GetGeneration(), + gwapiv1.RouteConditionAccepted, + metav1.ConditionFalse, + gwapiv1.RouteReasonUnsupportedValue, + errMsg, + ) + return + } + hm = &ir.HTTPHostModifier{ + Header: hrf.Spec.URLRewrite.Hostname.Header, + } + } else if hrf.Spec.URLRewrite.Hostname.Type == egv1a1.BackendHTTPHostnameModifier { + hm = &ir.HTTPHostModifier{ + Backend: ptr.To(true), + } + } + + if filterContext.HTTPFilterIR.URLRewrite != nil { + if filterContext.HTTPFilterIR.URLRewrite.Host == nil { + filterContext.HTTPFilterIR.URLRewrite.Host = hm + return + } + } else { // no url rewrite + filterContext.HTTPFilterIR.URLRewrite = &ir.URLRewrite{ + Host: hm, + } + return + } } - return + } } } diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-hostname-prefix-replace.out.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-hostname-prefix-replace.out.yaml index 1577ab27e64..8e3079c9bbe 100644 --- a/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-hostname-prefix-replace.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-hostname-prefix-replace.out.yaml @@ -144,7 +144,8 @@ xdsIR: name: "" prefix: / urlRewrite: - hostname: rewrite.com + host: + name: rewrite.com path: fullReplace: null prefixMatchReplace: /rewrite diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-hostname.out.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-hostname.out.yaml index 658725825f3..c0d8cce8b8a 100644 --- a/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-hostname.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-hostname.out.yaml @@ -141,4 +141,5 @@ xdsIR: name: "" prefix: / urlRewrite: - hostname: rewrite.com + host: + name: rewrite.com diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-invalid-filter-type.out.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-invalid-filter-type.out.yaml index d0d62e98a27..7cbff74f25b 100644 --- a/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-invalid-filter-type.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-invalid-filter-type.out.yaml @@ -141,4 +141,5 @@ xdsIR: name: "" prefix: / urlRewrite: - hostname: urlrewrite.envoyproxy.io + host: + name: urlrewrite.envoyproxy.io diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-http.out.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-http.out.yaml index 5c8d2527a1f..c42f3934568 100644 --- a/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-http.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-http.out.yaml @@ -308,7 +308,8 @@ xdsIR: name: "" prefix: /host-and-regex-path urlRewrite: - hostname: rewrite.com + host: + name: rewrite.com path: fullReplace: null prefixMatchReplace: null @@ -336,7 +337,8 @@ xdsIR: name: "" prefix: /regex-path-and-host urlRewrite: - hostname: rewrite.com + host: + name: rewrite.com - destination: name: httproute/default/httproute-1/rule/0 settings: diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-invalid.out.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-invalid.out.yaml index fb9e85a632d..17ffc680f52 100644 --- a/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-invalid.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-invalid.out.yaml @@ -164,9 +164,9 @@ httpRoutes: parents: - conditions: - lastTransitionTime: null - message: Route is accepted - reason: Accepted - status: "True" + message: Cannot configure multiple urlRewrite filters for a single HTTPRouteRule + reason: UnsupportedValue + status: "False" type: Accepted - lastTransitionTime: null message: Resolved all the Object references for the Route @@ -356,28 +356,3 @@ xdsIR: escapedSlashesAction: UnescapeAndRedirect mergeSlashes: true port: 10080 - routes: - - destination: - name: httproute/default/httproute-multiple-path-rewrites-1/rule/0 - settings: - - addressType: IP - endpoints: - - host: 7.7.7.7 - port: 8080 - protocol: HTTP - weight: 1 - hostname: gateway.envoyproxy.io - isHTTP2: false - metadata: - kind: HTTPRoute - name: httproute-multiple-path-rewrites-1 - namespace: default - name: httproute/default/httproute-multiple-path-rewrites-1/rule/0/match/0/gateway_envoyproxy_io - pathMatch: - distinct: false - name: "" - prefix: /ext-first - urlRewrite: - path: - fullReplace: null - prefixMatchReplace: /rewrite diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter-invalid.in.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter-invalid.in.yaml new file mode 100644 index 00000000000..5a7f4499048 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter-invalid.in.yaml @@ -0,0 +1,236 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: "*.envoyproxy.io" + allowedRoutes: + namespaces: + from: All +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-invalid-header + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/invalid-header" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: invalid-header + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-multiple-host-rewrites-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/ext-first" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header + - type: URLRewrite + urlRewrite: + hostname: "rewrite.com" + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-multiple-path-rewrites-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/inline-first" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: URLRewrite + urlRewrite: + hostname: "rewrite.com" + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-multiple-header-host-rewrites + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/two-headers" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header-2 + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-multiple-header-host-rewrites + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/two-backends" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-backend + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-backend-2 + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-header-and-backend-host-rewrites + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/header-and-backend" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header +httpFilters: + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: HTTPRouteFilter + metadata: + name: valid-header + namespace: default + spec: + urlRewrite: + hostname: + type: Header + header: my-host + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: HTTPRouteFilter + metadata: + name: valid-header-2 + namespace: default + spec: + urlRewrite: + hostname: + type: Header + header: my-host2 + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: HTTPRouteFilter + metadata: + name: valid-backend + namespace: default + spec: + urlRewrite: + hostname: + type: Backend + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: HTTPRouteFilter + metadata: + name: valid-backend-2 + namespace: default + spec: + urlRewrite: + hostname: + type: Backend + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: HTTPRouteFilter + metadata: + name: invalid-header + namespace: default + spec: + urlRewrite: + hostname: + type: Header diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter-invalid.out.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter-invalid.out.yaml new file mode 100644 index 00000000000..ab24ec0e81d --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter-invalid.out.yaml @@ -0,0 +1,364 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 6 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-invalid-header + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: invalid-header + type: ExtensionRef + matches: + - path: + value: /invalid-header + status: + parents: + - conditions: + - lastTransitionTime: null + message: Header must be set when rewrite path type is "Header" + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-multiple-host-rewrites-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header + type: ExtensionRef + - type: URLRewrite + urlRewrite: + hostname: rewrite.com + matches: + - path: + value: /ext-first + status: + parents: + - conditions: + - lastTransitionTime: null + message: Cannot configure multiple urlRewrite filters for a single HTTPRouteRule + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-multiple-path-rewrites-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - type: URLRewrite + urlRewrite: + hostname: rewrite.com + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header + type: ExtensionRef + matches: + - path: + value: /inline-first + status: + parents: + - conditions: + - lastTransitionTime: null + message: Cannot configure multiple urlRewrite filters for a single HTTPRouteRule + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-multiple-header-host-rewrites + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header + type: ExtensionRef + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header-2 + type: ExtensionRef + matches: + - path: + value: /two-headers + status: + parents: + - conditions: + - lastTransitionTime: null + message: Cannot configure multiple urlRewrite filters for a single HTTPRouteRule + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-multiple-header-host-rewrites + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-backend + type: ExtensionRef + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-backend-2 + type: ExtensionRef + matches: + - path: + value: /two-backends + status: + parents: + - conditions: + - lastTransitionTime: null + message: Cannot configure multiple urlRewrite filters for a single HTTPRouteRule + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-header-and-backend-host-rewrites + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header + type: ExtensionRef + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header + type: ExtensionRef + matches: + - path: + value: /header-and-backend + status: + parents: + - conditions: + - lastTransitionTime: null + message: Cannot configure multiple urlRewrite filters for a single HTTPRouteRule + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 +xdsIR: + envoy-gateway/gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.in.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.in.yaml new file mode 100644 index 00000000000..f39c951e5f8 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.in.yaml @@ -0,0 +1,147 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: "*.envoyproxy.io" + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/valid-header" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/valid-backend" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-backend +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-3 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/path-and-header-host" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: URLRewrite + urlRewrite: + path: + type: ReplacePrefixMatch + replacePrefixMatch: /rewrite + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-4 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/header-host-and-path" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header + - type: URLRewrite + urlRewrite: + path: + type: ReplacePrefixMatch + replacePrefixMatch: /rewrite +httpFilters: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: HTTPRouteFilter + metadata: + name: valid-header + namespace: default + spec: + urlRewrite: + hostname: + type: Header + header: my-host +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: HTTPRouteFilter + metadata: + name: valid-backend + namespace: default + spec: + urlRewrite: + hostname: + type: Backend diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.out.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.out.yaml new file mode 100644 index 00000000000..916f7d0cefe --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.out.yaml @@ -0,0 +1,362 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 4 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header + type: ExtensionRef + matches: + - path: + value: /valid-header + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-backend + type: ExtensionRef + matches: + - path: + value: /valid-backend + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-3 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - type: URLRewrite + urlRewrite: + path: + replacePrefixMatch: /rewrite + type: ReplacePrefixMatch + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header + type: ExtensionRef + matches: + - path: + value: /path-and-header-host + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-4 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: valid-header + type: ExtensionRef + - type: URLRewrite + urlRewrite: + path: + replacePrefixMatch: /rewrite + type: ReplacePrefixMatch + matches: + - path: + value: /header-host-and-path + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 +xdsIR: + envoy-gateway/gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-3/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-3 + namespace: default + name: httproute/default/httproute-3/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: /path-and-header-host + urlRewrite: + host: + header: my-host + path: + fullReplace: null + prefixMatchReplace: /rewrite + - destination: + name: httproute/default/httproute-4/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-4 + namespace: default + name: httproute/default/httproute-4/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: /header-host-and-path + urlRewrite: + path: + fullReplace: null + prefixMatchReplace: /rewrite + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: /valid-backend + urlRewrite: + host: + backend: true + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: /valid-header + urlRewrite: + host: + header: my-host diff --git a/internal/ir/xds.go b/internal/ir/xds.go index cb5021f4c9f..00edaf21b57 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -55,6 +55,7 @@ var ( ErrHTTPPathModifierDoubleReplace = errors.New("redirect filter cannot have a path modifier that supplies more than one of fullPathReplace, prefixMatchReplace and regexMatchReplace") ErrHTTPPathModifierNoReplace = errors.New("redirect filter cannot have a path modifier that does not supply either fullPathReplace, prefixMatchReplace or regexMatchReplace") ErrHTTPPathRegexModifierNoSetting = errors.New("redirect filter cannot have a path modifier that does not supply either fullPathReplace, prefixMatchReplace or regexMatchReplace") + ErrHTTPHostModifierDoubleReplace = errors.New("redirect filter cannot have a host modifier that supplies more than one of Hostname, Header and Backend") ErrAddHeaderEmptyName = errors.New("header modifier filter cannot configure a header without a name to be added") ErrAddHeaderDuplicate = errors.New("header modifier filter attempts to add the same header more than once (case insensitive)") ErrRemoveHeaderDuplicate = errors.New("header modifier filter attempts to remove the same header more than once (case insensitive)") @@ -1382,8 +1383,8 @@ func (r DirectResponse) Validate() error { type URLRewrite struct { // Path contains config for rewriting the path of the request. Path *ExtendedHTTPPathModifier `json:"path,omitempty" yaml:"path,omitempty"` - // Hostname configures the replacement of the request's hostname. - Hostname *string `json:"hostname,omitempty" yaml:"hostname,omitempty"` + // Host configures the replacement of the request's host header. + Host *HTTPHostModifier `json:"host,omitempty" yaml:"host,omitempty"` } // Validate the fields within the URLRewrite structure @@ -1396,6 +1397,12 @@ func (r URLRewrite) Validate() error { } } + if r.Host != nil { + if err := r.Host.Validate(); err != nil { + errs = errors.Join(errs, err) + } + } + return errs } @@ -1499,6 +1506,34 @@ func (r ExtendedHTTPPathModifier) Validate() error { return errs } +// HTTPHostModifier holds instructions for how to modify the host of a request +// with both core gateway-api and extended envoy gateway capabilities +// +k8s:deepcopy-gen=true +type HTTPHostModifier struct { + Name *string `json:"name,omitempty" yaml:"name,omitempty"` + Header *string `json:"header,omitempty" yaml:"header,omitempty"` + Backend *bool `json:"backend,omitempty" yaml:"backend,omitempty"` +} + +// Validate the fields within the HTTPPathModifier structure +func (r HTTPHostModifier) Validate() error { + var errs error + + rewrites := []bool{r.Name != nil, r.Header != nil, r.Backend != nil} + rwc := 0 + for _, rw := range rewrites { + if rw { + rwc++ + } + } + + if rwc > 1 { + errs = errors.Join(errs, ErrHTTPHostModifierDoubleReplace) + } + + return errs +} + // StringMatch holds the various match conditions. // Only one of Exact, Prefix, SafeRegex or Distinct can be set. // +k8s:deepcopy-gen=true diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index 5ff9a8736ef..b4593152593 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -308,7 +308,9 @@ var ( Exact: ptr.To("rewrite"), }, URLRewrite: &URLRewrite{ - Hostname: ptr.To("rewrite.example.com"), + Host: &HTTPHostModifier{ + Name: ptr.To("rewrite.example.com"), + }, Path: &ExtendedHTTPPathModifier{ HTTPPathModifier: HTTPPathModifier{ FullReplace: ptr.To("/rewrite"), @@ -324,7 +326,9 @@ var ( Exact: ptr.To("rewrite"), }, URLRewrite: &URLRewrite{ - Hostname: ptr.To("rewrite.example.com"), + Host: &HTTPHostModifier{ + Name: ptr.To("rewrite.example.com"), + }, Path: &ExtendedHTTPPathModifier{ HTTPPathModifier: HTTPPathModifier{ FullReplace: ptr.To("/rewrite"), diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 791b6d5dd68..111f2661377 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -1270,6 +1270,36 @@ func (in *HTTPHealthChecker) DeepCopy() *HTTPHealthChecker { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPHostModifier) DeepCopyInto(out *HTTPHostModifier) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.Header != nil { + in, out := &in.Header, &out.Header + *out = new(string) + **out = **in + } + if in.Backend != nil { + in, out := &in.Backend, &out.Backend + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPHostModifier. +func (in *HTTPHostModifier) DeepCopy() *HTTPHostModifier { + if in == nil { + return nil + } + out := new(HTTPHostModifier) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPListener) DeepCopyInto(out *HTTPListener) { *out = *in @@ -3341,10 +3371,10 @@ func (in *URLRewrite) DeepCopyInto(out *URLRewrite) { *out = new(ExtendedHTTPPathModifier) (*in).DeepCopyInto(*out) } - if in.Hostname != nil { - in, out := &in.Hostname, &out.Hostname - *out = new(string) - **out = **in + if in.Host != nil { + in, out := &in.Host, &out.Host + *out = new(HTTPHostModifier) + (*in).DeepCopyInto(*out) } } diff --git a/internal/xds/translator/route.go b/internal/xds/translator/route.go index a8ec4a291d5..e1d790268dc 100644 --- a/internal/xds/translator/route.go +++ b/internal/xds/translator/route.go @@ -417,9 +417,21 @@ func buildXdsURLRewriteAction(destName string, urlRewrite *ir.URLRewrite, pathMa } } - if urlRewrite.Hostname != nil { - routeAction.HostRewriteSpecifier = &routev3.RouteAction_HostRewriteLiteral{ - HostRewriteLiteral: *urlRewrite.Hostname, + if urlRewrite.Host != nil { + + switch { + case urlRewrite.Host.Name != nil: + routeAction.HostRewriteSpecifier = &routev3.RouteAction_HostRewriteLiteral{ + HostRewriteLiteral: *urlRewrite.Host.Name, + } + case urlRewrite.Host.Header != nil: + routeAction.HostRewriteSpecifier = &routev3.RouteAction_HostRewriteHeader{ + HostRewriteHeader: *urlRewrite.Host.Header, + } + case urlRewrite.Host.Backend != nil: + routeAction.HostRewriteSpecifier = &routev3.RouteAction_AutoHostRewrite{ + AutoHostRewrite: wrapperspb.Bool(true), + } } routeAction.AppendXForwardedHost = true diff --git a/internal/xds/translator/testdata/in/xds-ir/http-route-rewrite-url-host.yaml b/internal/xds/translator/testdata/in/xds-ir/http-route-rewrite-url-host.yaml index 8cc673a7e5b..525a22210b9 100644 --- a/internal/xds/translator/testdata/in/xds-ir/http-route-rewrite-url-host.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/http-route-rewrite-url-host.yaml @@ -23,6 +23,43 @@ http: - host: "1.2.3.4" port: 50000 urlRewrite: - hostname: "3.3.3.3" + host: + name: "3.3.3.3" + path: + prefixMatchReplace: /rewrite + - name: "rewrite-host-header" + pathMatch: + prefix: "/host-header" + hostname: gateway.envoyproxy.io + headerMatches: + - name: ":authority" + exact: gateway.envoyproxy.io + destination: + name: "rewrite-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 + urlRewrite: + host: + header: "foo" + path: + prefixMatchReplace: /rewrite + - name: "rewrite-host-backend" + pathMatch: + prefix: "/host-backend" + hostname: gateway.envoyproxy.io + headerMatches: + - name: ":authority" + exact: gateway.envoyproxy.io + destination: + name: "rewrite-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 + urlRewrite: + host: + backend: true path: prefixMatchReplace: /rewrite diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-host.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-host.routes.yaml index 680a67404ee..a3e1e29e821 100644 --- a/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-host.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-host.routes.yaml @@ -19,3 +19,31 @@ prefixRewrite: /rewrite upgradeConfigs: - upgradeType: websocket + - match: + headers: + - name: :authority + stringMatch: + exact: gateway.envoyproxy.io + pathSeparatedPrefix: /host-header + name: rewrite-host-header + route: + appendXForwardedHost: true + cluster: rewrite-route-dest + hostRewriteHeader: foo + prefixRewrite: /rewrite + upgradeConfigs: + - upgradeType: websocket + - match: + headers: + - name: :authority + stringMatch: + exact: gateway.envoyproxy.io + pathSeparatedPrefix: /host-backend + name: rewrite-host-backend + route: + appendXForwardedHost: true + autoHostRewrite: true + cluster: rewrite-route-dest + prefixRewrite: /rewrite + upgradeConfigs: + - upgradeType: websocket diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index c183a4f0b8f..f90ee0702ad 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -1971,7 +1971,7 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | | `type` | _[HTTPHostnameModifierType](#httphostnamemodifiertype)_ | true | | -| `setFromHeader` | _string_ | false | SetFromHeader is the name of the header whose value would be used to rewrite the Host header | +| `header` | _string_ | false | Header is the name of the header whose value would be used to rewrite the Host header | #### HTTPHostnameModifierType @@ -1985,8 +1985,8 @@ _Appears in:_ | Value | Description | | ----- | ----------- | -| `SetFromHeader` | HeaderHTTPHostnameModifier indicates that the Host header value would be replaced with the value of the header specified in setFromHeader.
https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-host-rewrite-header
| -| `SetFromBackend` | BackendHTTPHostnameModifier indicates that the Host header value would be replaced by the DNS name of the backend if it exists.
https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-auto-host-rewrite
| +| `Header` | HeaderHTTPHostnameModifier indicates that the Host header value would be replaced with the value of the header specified in header.
https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-host-rewrite-header
| +| `Backend` | BackendHTTPHostnameModifier indicates that the Host header value would be replaced by the DNS name of the backend if it exists.
https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-auto-host-rewrite
| #### HTTPPathModifier @@ -2106,6 +2106,7 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | +| `hostname` | _[HTTPHostnameModifier](#httphostnamemodifier)_ | false | Hostname is the value to be used to replace the Host header value during
forwarding. | | `path` | _[HTTPPathModifier](#httppathmodifier)_ | false | Path defines a path rewrite. | diff --git a/site/content/en/latest/tasks/traffic/http-urlrewrite.md b/site/content/en/latest/tasks/traffic/http-urlrewrite.md index a643d775a57..3515bd9caa4 100644 --- a/site/content/en/latest/tasks/traffic/http-urlrewrite.md +++ b/site/content/en/latest/tasks/traffic/http-urlrewrite.md @@ -374,14 +374,14 @@ spec: The HTTPRoute status should indicate that it has been accepted and is bound to the example Gateway. ```shell -kubectl get httproute/http-filter-url-rewrite -o yaml +kubectl get httproute/http-filter-url-regex-rewrite -o yaml ``` -Querying `http://${GATEWAY_HOST}/get/origin/path/extra` should rewrite the request to -`http://${GATEWAY_HOST}/force/replace/fullpath`. +Querying `http://${GATEWAY_HOST}/service/foo/v1/api` should rewrite the request to +`http://${GATEWAY_HOST}/service/foo/v1/api`. ```console -$ curl -L -vvv --header "Host: path.regex.rewrite.example" "http://${GATEWAY_HOST}/get/origin/path/extra" +$ curl -L -vvv --header "Host: path.regex.rewrite.example" "http://${GATEWAY_HOST}/service/foo/v1/api" ... > GET /service/foo/v1/api HTTP/1.1 > Host: path.regex.rewrite.example @@ -555,6 +555,145 @@ $ curl -L -vvv --header "Host: path.rewrite.example" "http://${GATEWAY_HOST}/get You can see that the `X-Forwarded-Host` is `path.rewrite.example`, but the actual host is `envoygateway.io`. +## Rewrite URL Host Name by Header or Backend + +In addition to core Gateway-API rewrite options, Envoy Gateway supports extended rewrite options through the [HTTPRouteFilter][] API. +The `HTTPRouteFilter` API can be configured to rewrite the Host header value to: +- The value of a different request header +- The DNS name of the backend that the request is routed to + +In the following example, the host header is rewritten to the value of the x-custom-host header. + +{{< tabpane text=true >}} +{{% tab header="Apply from stdin" %}} + +```shell +cat <}} + +The HTTPRoute status should indicate that it has been accepted and is bound to the example Gateway. + +```shell +kubectl get httproute/http-filter-header-host-rewrite -o yaml +``` + +Querying `http://${GATEWAY_HOST}/header` and providing a custom host rewrite header x-custom-host should rewrite the +request host header to the value of the x-custom-host header. + +```console +$ curl -L -vvv --header "Host: host.header.rewrite.example" --header "x-custom-host: foo" "http://${GATEWAY_HOST}/header" +... +> GET /header HTTP/1.1 +> Host: host.header.rewrite.example +> User-Agent: curl/8.7.1 +> Accept: */* +> x-custom-host: foo +> +* Request completely sent off +< HTTP/1.1 200 OK +< +{ + "path": "/header", + "host": "foo", + "method": "GET", + "proto": "HTTP/1.1", + "headers": { + "X-Custom-Host": [ + "foo" + ], + "X-Forwarded-Host": [ + "host.header.rewrite.example" + ], + }, + "namespace": "default", + "ingress": "", + "service": "", + "pod": "backend-765694d47f-5t6f2" +... +``` + +You can see that the host is rewritten from `host.header.rewrite.example`, to the value of the provided +`x-custom-host` header `foo`. The original host header is preserved in the `X-Forwarded-Host` header. + + [HTTPURLRewriteFilter]: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPURLRewriteFilter [HTTPRouteFilter]: ../../../api/extension_types#httproutefilter [RE2]: https://github.com/google/re2/wiki/Syntax \ No newline at end of file diff --git a/site/content/zh/latest/api/extension_types.md b/site/content/zh/latest/api/extension_types.md index c183a4f0b8f..f90ee0702ad 100644 --- a/site/content/zh/latest/api/extension_types.md +++ b/site/content/zh/latest/api/extension_types.md @@ -1971,7 +1971,7 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | | `type` | _[HTTPHostnameModifierType](#httphostnamemodifiertype)_ | true | | -| `setFromHeader` | _string_ | false | SetFromHeader is the name of the header whose value would be used to rewrite the Host header | +| `header` | _string_ | false | Header is the name of the header whose value would be used to rewrite the Host header | #### HTTPHostnameModifierType @@ -1985,8 +1985,8 @@ _Appears in:_ | Value | Description | | ----- | ----------- | -| `SetFromHeader` | HeaderHTTPHostnameModifier indicates that the Host header value would be replaced with the value of the header specified in setFromHeader.
https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-host-rewrite-header
| -| `SetFromBackend` | BackendHTTPHostnameModifier indicates that the Host header value would be replaced by the DNS name of the backend if it exists.
https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-auto-host-rewrite
| +| `Header` | HeaderHTTPHostnameModifier indicates that the Host header value would be replaced with the value of the header specified in header.
https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-host-rewrite-header
| +| `Backend` | BackendHTTPHostnameModifier indicates that the Host header value would be replaced by the DNS name of the backend if it exists.
https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-auto-host-rewrite
| #### HTTPPathModifier @@ -2106,6 +2106,7 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | +| `hostname` | _[HTTPHostnameModifier](#httphostnamemodifier)_ | false | Hostname is the value to be used to replace the Host header value during
forwarding. | | `path` | _[HTTPPathModifier](#httppathmodifier)_ | false | Path defines a path rewrite. | diff --git a/test/cel-validation/httproutefilter_test.go b/test/cel-validation/httproutefilter_test.go index 7f84deb71e5..c0b6e1ec817 100644 --- a/test/cel-validation/httproutefilter_test.go +++ b/test/cel-validation/httproutefilter_test.go @@ -86,13 +86,13 @@ func TestHTTPRouteFilter(t *testing.T) { }, }, { - desc: "Valid SetFromHeader", + desc: "Valid Header", mutate: func(httproutefilter *egv1a1.HTTPRouteFilter) { httproutefilter.Spec = egv1a1.HTTPRouteFilterSpec{ URLRewrite: &egv1a1.HTTPURLRewriteFilter{ Hostname: &egv1a1.HTTPHostnameModifier{ - Type: egv1a1.HeaderHTTPHostnameModifier, - SetFromHeader: ptr.To("foo"), + Type: egv1a1.HeaderHTTPHostnameModifier, + Header: ptr.To("foo"), }, }, } @@ -113,7 +113,7 @@ func TestHTTPRouteFilter(t *testing.T) { wantErrors: []string{}, }, { - desc: "invalid SetFromHeader missing settings", + desc: "invalid Header missing settings", mutate: func(httproutefilter *egv1a1.HTTPRouteFilter) { httproutefilter.Spec = egv1a1.HTTPRouteFilterSpec{ URLRewrite: &egv1a1.HTTPURLRewriteFilter{ @@ -123,7 +123,7 @@ func TestHTTPRouteFilter(t *testing.T) { }, } }, - wantErrors: []string{"spec.urlRewrite.hostname: Invalid value: \"object\": setFromHeader must be specified for SetFromHeader type"}, + wantErrors: []string{"spec.urlRewrite.hostname: Invalid value: \"object\": header must be specified for Header type"}, }, { desc: "invalid SetFromBackend type", @@ -131,13 +131,13 @@ func TestHTTPRouteFilter(t *testing.T) { httproutefilter.Spec = egv1a1.HTTPRouteFilterSpec{ URLRewrite: &egv1a1.HTTPURLRewriteFilter{ Hostname: &egv1a1.HTTPHostnameModifier{ - Type: egv1a1.BackendHTTPHostnameModifier, - SetFromHeader: ptr.To("foo"), + Type: egv1a1.BackendHTTPHostnameModifier, + Header: ptr.To("foo"), }, }, } }, - wantErrors: []string{"spec.urlRewrite.hostname: Invalid value: \"object\": setFromHeader must be nil if the type is not SetFromHeader"}, + wantErrors: []string{"spec.urlRewrite.hostname: Invalid value: \"object\": header must be nil if the type is not Header"}, }, } diff --git a/test/e2e/testdata/httproute-rewrite-host.yaml b/test/e2e/testdata/httproute-rewrite-host.yaml new file mode 100644 index 00000000000..871b2008b3b --- /dev/null +++ b/test/e2e/testdata/httproute-rewrite-host.yaml @@ -0,0 +1,68 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: rewrite-host + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - matches: + - path: + type: PathPrefix + value: /header + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: header-host-rewrite + backendRefs: + - name: infra-backend-v1 + port: 8080 + - matches: + - path: + type: PathPrefix + value: /backend + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: backend-host-rewrite + backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-fqdn +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: HTTPRouteFilter +metadata: + name: header-host-rewrite + namespace: gateway-conformance-infra +spec: + urlRewrite: + hostname: + type: Header + header: x-custom-host +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: HTTPRouteFilter +metadata: + name: backend-host-rewrite + namespace: gateway-conformance-infra +spec: + urlRewrite: + hostname: + type: Backend +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: backend-fqdn + namespace: gateway-conformance-infra +spec: + endpoints: + - fqdn: + hostname: infra-backend-v1.gateway-conformance-infra.svc.cluster.local + port: 8080 diff --git a/test/e2e/tests/httproute_rewrite_host.go b/test/e2e/tests/httproute_rewrite_host.go new file mode 100644 index 00000000000..643d0891e9f --- /dev/null +++ b/test/e2e/tests/httproute_rewrite_host.go @@ -0,0 +1,75 @@ +// 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. + +//go:build e2e + +package tests + +import ( + "testing" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/gateway-api/conformance/utils/http" + "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api/conformance/utils/suite" +) + +func init() { + ConformanceTests = append(ConformanceTests, HTTPRouteRewriteHostHeader) +} + +var HTTPRouteRewriteHostHeader = suite.ConformanceTest{ + ShortName: "HTTPRouteRewriteHostHeader", + Description: "An HTTPRoute with host rewrite filter to rewrite a host header", + Manifests: []string{"testdata/httproute-rewrite-host.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "rewrite-host", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) + + testCases := []http.ExpectedResponse{ + { + Request: http.Request{ + Path: "/header", + Headers: map[string]string{ + "x-custom-host": "custom-host", + }, + }, + ExpectedRequest: &http.ExpectedRequest{ + Request: http.Request{ + Path: "/header", + Host: "custom-host", + }, + }, + Backend: "infra-backend-v1", + Namespace: ns, + }, + { + Request: http.Request{ + Path: "/backend", + }, + ExpectedRequest: &http.ExpectedRequest{ + Request: http.Request{ + Path: "/backend", + Host: "infra-backend-v1.gateway-conformance-infra.svc.cluster.local", + }, + }, + Backend: "infra-backend-v1", + Namespace: ns, + }, + } + for i := range testCases { + // Declare tc here to avoid loop variable + // reuse issues across parallel tests. + tc := testCases[i] + t.Run(tc.GetTestCaseName(i), func(t *testing.T) { + t.Parallel() + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc) + }) + } + }, +}