diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml index bd99a6c36751..c29fe1add8b2 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.defaults.golden.yaml @@ -1283,7 +1283,40 @@ spec: - RequestHeaderModifier - ResponseHeaderModifier - RequestRedirect + - URLRewrite type: string + urlRewrite: + properties: + hostname: + description: "PreciseHostname is the fully + qualified domain name of a network host. + This matches the RFC 1123 definition of + a hostname with 1 notable exception that + numeric IP addresses are not allowed. + \n Note that as per RFC1035 and RFC1123, + a *label* must consist of lower case alphanumeric + characters or '-', and must start and + end with an alphanumeric character. No + other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + properties: + replaceFullPath: + type: string + replacePrefixMatch: + type: string + type: + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object required: - type type: object diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present-not-enabled.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present-not-enabled.yaml index bd99a6c36751..c29fe1add8b2 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present-not-enabled.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present-not-enabled.yaml @@ -1283,7 +1283,40 @@ spec: - RequestHeaderModifier - ResponseHeaderModifier - RequestRedirect + - URLRewrite type: string + urlRewrite: + properties: + hostname: + description: "PreciseHostname is the fully + qualified domain name of a network host. + This matches the RFC 1123 definition of + a hostname with 1 notable exception that + numeric IP addresses are not allowed. + \n Note that as per RFC1035 and RFC1123, + a *label* must consist of lower case alphanumeric + characters or '-', and must start and + end with an alphanumeric character. No + other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + properties: + replaceFullPath: + type: string + replacePrefixMatch: + type: string + type: + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object required: - type type: object diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml index d860e89597d5..09d0237002dc 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.gateway-api-present.yaml @@ -1435,7 +1435,40 @@ spec: - RequestHeaderModifier - ResponseHeaderModifier - RequestRedirect + - URLRewrite type: string + urlRewrite: + properties: + hostname: + description: "PreciseHostname is the fully + qualified domain name of a network host. + This matches the RFC 1123 definition of + a hostname with 1 notable exception that + numeric IP addresses are not allowed. + \n Note that as per RFC1035 and RFC1123, + a *label* must consist of lower case alphanumeric + characters or '-', and must start and + end with an alphanumeric character. No + other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + properties: + replaceFullPath: + type: string + replacePrefixMatch: + type: string + type: + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object required: - type type: object diff --git a/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml b/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml index 20e2e44b1ce0..740fb5cab3ef 100644 --- a/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml +++ b/app/kumactl/cmd/install/testdata/install-control-plane.with-helm-set.yaml @@ -1303,7 +1303,40 @@ spec: - RequestHeaderModifier - ResponseHeaderModifier - RequestRedirect + - URLRewrite type: string + urlRewrite: + properties: + hostname: + description: "PreciseHostname is the fully + qualified domain name of a network host. + This matches the RFC 1123 definition of + a hostname with 1 notable exception that + numeric IP addresses are not allowed. + \n Note that as per RFC1035 and RFC1123, + a *label* must consist of lower case alphanumeric + characters or '-', and must start and + end with an alphanumeric character. No + other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + properties: + replaceFullPath: + type: string + replacePrefixMatch: + type: string + type: + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object required: - type type: object diff --git a/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml b/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml index cac2a3086650..b3b03ffdd9ec 100644 --- a/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-crds.all.golden.yaml @@ -2454,7 +2454,40 @@ spec: - RequestHeaderModifier - ResponseHeaderModifier - RequestRedirect + - URLRewrite type: string + urlRewrite: + properties: + hostname: + description: "PreciseHostname is the fully + qualified domain name of a network host. + This matches the RFC 1123 definition of + a hostname with 1 notable exception that + numeric IP addresses are not allowed. + \n Note that as per RFC1035 and RFC1123, + a *label* must consist of lower case alphanumeric + characters or '-', and must start and + end with an alphanumeric character. No + other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + properties: + replaceFullPath: + type: string + replacePrefixMatch: + type: string + type: + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object required: - type type: object diff --git a/app/kumactl/cmd/install/testdata/install-crds.experimental-gatewayapi.golden.yaml b/app/kumactl/cmd/install/testdata/install-crds.experimental-gatewayapi.golden.yaml index c480af0c66ee..59b9d709cf17 100644 --- a/app/kumactl/cmd/install/testdata/install-crds.experimental-gatewayapi.golden.yaml +++ b/app/kumactl/cmd/install/testdata/install-crds.experimental-gatewayapi.golden.yaml @@ -2606,7 +2606,40 @@ spec: - RequestHeaderModifier - ResponseHeaderModifier - RequestRedirect + - URLRewrite type: string + urlRewrite: + properties: + hostname: + description: "PreciseHostname is the fully + qualified domain name of a network host. + This matches the RFC 1123 definition of + a hostname with 1 notable exception that + numeric IP addresses are not allowed. + \n Note that as per RFC1035 and RFC1123, + a *label* must consist of lower case alphanumeric + characters or '-', and must start and + end with an alphanumeric character. No + other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + properties: + replaceFullPath: + type: string + replacePrefixMatch: + type: string + type: + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object required: - type type: object diff --git a/deployments/charts/kuma/crds/kuma.io_meshhttproutes.yaml b/deployments/charts/kuma/crds/kuma.io_meshhttproutes.yaml index 1ae2007882a6..d8c1b4c4e40e 100644 --- a/deployments/charts/kuma/crds/kuma.io_meshhttproutes.yaml +++ b/deployments/charts/kuma/crds/kuma.io_meshhttproutes.yaml @@ -265,7 +265,40 @@ spec: - RequestHeaderModifier - ResponseHeaderModifier - RequestRedirect + - URLRewrite type: string + urlRewrite: + properties: + hostname: + description: "PreciseHostname is the fully + qualified domain name of a network host. + This matches the RFC 1123 definition of + a hostname with 1 notable exception that + numeric IP addresses are not allowed. + \n Note that as per RFC1035 and RFC1123, + a *label* must consist of lower case alphanumeric + characters or '-', and must start and + end with an alphanumeric character. No + other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + properties: + replaceFullPath: + type: string + replacePrefixMatch: + type: string + type: + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object required: - type type: object diff --git a/pkg/plugins/policies/meshhttproute/api/v1alpha1/meshhttproute.go b/pkg/plugins/policies/meshhttproute/api/v1alpha1/meshhttproute.go index 355c1eb1a6e1..89b9e91a91cc 100644 --- a/pkg/plugins/policies/meshhttproute/api/v1alpha1/meshhttproute.go +++ b/pkg/plugins/policies/meshhttproute/api/v1alpha1/meshhttproute.go @@ -87,13 +87,14 @@ type RuleConf struct { BackendRefs *[]BackendRef `json:"backendRefs,omitempty"` } -// +kubebuilder:validation:Enum=RequestHeaderModifier;ResponseHeaderModifier;RequestRedirect +// +kubebuilder:validation:Enum=RequestHeaderModifier;ResponseHeaderModifier;RequestRedirect;URLRewrite type FilterType string const ( RequestHeaderModifierType FilterType = "RequestHeaderModifier" ResponseHeaderModifierType FilterType = "ResponseHeaderModifier" RequestRedirectType FilterType = "RequestRedirect" + URLRewriteType FilterType = "URLRewrite" ) // +kubebuilder:validation:MinLength=1 @@ -157,11 +158,31 @@ type RequestRedirect struct { StatusCode *int `json:"statusCode,omitempty"` } +// +kubebuilder:validation:Enum=ReplaceFullPath;ReplacePrefixMatch +type PathRewriteType string + +const ( + ReplaceFullPathType PathRewriteType = "ReplaceFullPath" + ReplacePrefixMatchType PathRewriteType = "ReplacePrefixMatch" +) + +type PathRewrite struct { + Type PathRewriteType `json:"type"` + ReplaceFullPath *string `json:"replaceFullPath,omitempty"` + ReplacePrefixMatch *string `json:"replacePrefixMatch,omitempty"` +} + +type URLRewrite struct { + Hostname *PreciseHostname `json:"hostname,omitempty"` + Path *PathRewrite `json:"path,omitempty"` +} + type Filter struct { Type FilterType `json:"type"` RequestHeaderModifier *HeaderModifier `json:"requestHeaderModifier,omitempty"` ResponseHeaderModifier *HeaderModifier `json:"responseHeaderModifier,omitempty"` RequestRedirect *RequestRedirect `json:"requestRedirect,omitempty"` + URLRewrite *URLRewrite `json:"urlRewrite,omitempty"` // TODO: add path to redirect after adding URL rewrite } diff --git a/pkg/plugins/policies/meshhttproute/api/v1alpha1/schema.yaml b/pkg/plugins/policies/meshhttproute/api/v1alpha1/schema.yaml index 177bf743e484..6419b69aa2de 100644 --- a/pkg/plugins/policies/meshhttproute/api/v1alpha1/schema.yaml +++ b/pkg/plugins/policies/meshhttproute/api/v1alpha1/schema.yaml @@ -209,7 +209,31 @@ properties: - RequestHeaderModifier - ResponseHeaderModifier - RequestRedirect + - URLRewrite type: string + urlRewrite: + properties: + hostname: + description: "PreciseHostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 1 notable exception that numeric IP addresses are not allowed. \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + properties: + replaceFullPath: + type: string + replacePrefixMatch: + type: string + type: + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object required: - type type: object diff --git a/pkg/plugins/policies/meshhttproute/api/v1alpha1/zz_generated.deepcopy.go b/pkg/plugins/policies/meshhttproute/api/v1alpha1/zz_generated.deepcopy.go index ccbf8ed79e33..75fe5a8d9333 100644 --- a/pkg/plugins/policies/meshhttproute/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/plugins/policies/meshhttproute/api/v1alpha1/zz_generated.deepcopy.go @@ -41,6 +41,11 @@ func (in *Filter) DeepCopyInto(out *Filter) { *out = new(RequestRedirect) (*in).DeepCopyInto(*out) } + if in.URLRewrite != nil { + in, out := &in.URLRewrite, &out.URLRewrite + *out = new(URLRewrite) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Filter. @@ -166,6 +171,31 @@ func (in *PathMatch) DeepCopy() *PathMatch { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PathRewrite) DeepCopyInto(out *PathRewrite) { + *out = *in + if in.ReplaceFullPath != nil { + in, out := &in.ReplaceFullPath, &out.ReplaceFullPath + *out = new(string) + **out = **in + } + if in.ReplacePrefixMatch != nil { + in, out := &in.ReplacePrefixMatch, &out.ReplacePrefixMatch + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PathRewrite. +func (in *PathRewrite) DeepCopy() *PathRewrite { + if in == nil { + return nil + } + out := new(PathRewrite) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *QueryParamsMatch) DeepCopyInto(out *QueryParamsMatch) { *out = *in @@ -298,3 +328,28 @@ func (in *To) DeepCopy() *To { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *URLRewrite) DeepCopyInto(out *URLRewrite) { + *out = *in + if in.Hostname != nil { + in, out := &in.Hostname, &out.Hostname + *out = new(PreciseHostname) + **out = **in + } + if in.Path != nil { + in, out := &in.Path, &out.Path + *out = new(PathRewrite) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new URLRewrite. +func (in *URLRewrite) DeepCopy() *URLRewrite { + if in == nil { + return nil + } + out := new(URLRewrite) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/plugins/policies/meshhttproute/k8s/crd/kuma.io_meshhttproutes.yaml b/pkg/plugins/policies/meshhttproute/k8s/crd/kuma.io_meshhttproutes.yaml index 1ae2007882a6..d8c1b4c4e40e 100644 --- a/pkg/plugins/policies/meshhttproute/k8s/crd/kuma.io_meshhttproutes.yaml +++ b/pkg/plugins/policies/meshhttproute/k8s/crd/kuma.io_meshhttproutes.yaml @@ -265,7 +265,40 @@ spec: - RequestHeaderModifier - ResponseHeaderModifier - RequestRedirect + - URLRewrite type: string + urlRewrite: + properties: + hostname: + description: "PreciseHostname is the fully + qualified domain name of a network host. + This matches the RFC 1123 definition of + a hostname with 1 notable exception that + numeric IP addresses are not allowed. + \n Note that as per RFC1035 and RFC1123, + a *label* must consist of lower case alphanumeric + characters or '-', and must start and + end with an alphanumeric character. No + other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + properties: + replaceFullPath: + type: string + replacePrefixMatch: + type: string + type: + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object required: - type type: object diff --git a/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/plugin_test.go b/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/plugin_test.go index bf4467d12d1f..05195de6fe9a 100644 --- a/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/plugin_test.go +++ b/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/plugin_test.go @@ -249,7 +249,7 @@ var _ = Describe("MeshHTTPRoute", func() { Secrets: &xds.TestSecrets{}, }, Mesh: xds_context.MeshContext{ - Resource: builders.Mesh().WithName("default").Build(), + Resource: samples.MeshDefault(), EndpointMap: outboundTargets, }, }, @@ -333,7 +333,7 @@ var _ = Describe("MeshHTTPRoute", func() { Secrets: &xds.TestSecrets{}, }, Mesh: xds_context.MeshContext{ - Resource: builders.Mesh().WithName("default").Build(), + Resource: samples.MeshDefault(), EndpointMap: outboundTargets, }, }, @@ -407,6 +407,65 @@ var _ = Describe("MeshHTTPRoute", func() { }, }, } + }()), Entry("url-rewrite", func() outboundsTestCase { + outboundTargets := core_xds.EndpointMap{ + "backend": []core_xds.Endpoint{{ + Target: "192.168.0.4", + Port: 8084, + Tags: map[string]string{"kuma.io/service": "backend", "kuma.io/protocol": "http", "region": "us"}, + Weight: 1, + }}, + } + return outboundsTestCase{ + xdsContext: xds_context.Context{ + ControlPlane: &xds_context.ControlPlaneContext{ + Secrets: &xds.TestSecrets{}, + }, + Mesh: xds_context.MeshContext{ + Resource: samples.MeshDefault(), + EndpointMap: outboundTargets, + }, + }, + proxy: core_xds.Proxy{ + APIVersion: xds_envoy.APIV3, + Dataplane: samples.DataplaneWeb(), + Routing: core_xds.Routing{ + OutboundTargets: outboundTargets, + }, + Policies: core_xds.MatchedPolicies{ + Dynamic: map[core_model.ResourceType]core_xds.TypedMatchingPolicies{ + api.MeshHTTPRouteType: { + ToRules: core_xds.ToRules{ + Rules: core_xds.Rules{{ + Subset: core_xds.MeshService("backend"), + Conf: api.PolicyDefault{ + Rules: []api.Rule{{ + Matches: []api.Match{{ + Path: &api.PathMatch{ + Type: api.Prefix, + Value: "/v1", + }, + }}, + Default: api.RuleConf{ + Filters: &[]api.Filter{{ + Type: api.URLRewriteType, + URLRewrite: &api.URLRewrite{ + Path: &api.PathRewrite{ + Type: api.ReplacePrefixMatchType, + ReplacePrefixMatch: pointer.To("/v2"), + }, + }, + }}, + }, + }}, + }}, + }, + }, + }, + }, + }, + }, + } }()), ) }) diff --git a/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/testdata/url-rewrite.clusters.golden.yaml b/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/testdata/url-rewrite.clusters.golden.yaml new file mode 100644 index 000000000000..69e536007ad8 --- /dev/null +++ b/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/testdata/url-rewrite.clusters.golden.yaml @@ -0,0 +1,15 @@ +resources: +- name: backend + resource: + '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + name: backend + type: EDS + typedExtensionProtocolOptions: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicitHttpConfig: + http2ProtocolOptions: {} diff --git a/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/testdata/url-rewrite.endpoints.golden.yaml b/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/testdata/url-rewrite.endpoints.golden.yaml new file mode 100644 index 000000000000..d6600e0c3053 --- /dev/null +++ b/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/testdata/url-rewrite.endpoints.golden.yaml @@ -0,0 +1,21 @@ +resources: +- name: backend + resource: + '@type': type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment + clusterName: backend + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 192.168.0.4 + portValue: 8084 + loadBalancingWeight: 1 + metadata: + filterMetadata: + envoy.lb: + kuma.io/protocol: http + region: us + envoy.transport_socket_match: + kuma.io/protocol: http + region: us diff --git a/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/testdata/url-rewrite.listeners.golden.yaml b/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/testdata/url-rewrite.listeners.golden.yaml new file mode 100644 index 000000000000..82e461c46496 --- /dev/null +++ b/pkg/plugins/policies/meshhttproute/plugin/v1alpha1/testdata/url-rewrite.listeners.golden.yaml @@ -0,0 +1,54 @@ +resources: +- name: outbound:127.0.0.1:10001 + resource: + '@type': type.googleapis.com/envoy.config.listener.v3.Listener + address: + socketAddress: + address: 127.0.0.1 + portValue: 10001 + filterChains: + - filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + httpFilters: + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + normalizePath: true + routeConfig: + name: outbound:backend + requestHeadersToAdd: + - header: + key: x-kuma-tags + value: '&kuma.io/protocol=http&&kuma.io/service=web&' + validateClusters: false + virtualHosts: + - domains: + - '*' + name: backend + routes: + - match: + path: /v1 + route: + regexRewrite: + pattern: + googleRe2: {} + regex: .* + substitution: /v2 + - match: + prefix: /v1/ + route: + prefixRewrite: /v2 + - match: + prefix: / + route: + cluster: backend + timeout: 0s + statPrefix: backend + metadata: + filterMetadata: + io.kuma.tags: + kuma.io/service: backend + name: outbound:127.0.0.1:10001 + trafficDirection: OUTBOUND diff --git a/pkg/plugins/policies/meshhttproute/xds/configurer.go b/pkg/plugins/policies/meshhttproute/xds/configurer.go index 00ad55500b97..200bcec20f4c 100644 --- a/pkg/plugins/policies/meshhttproute/xds/configurer.go +++ b/pkg/plugins/policies/meshhttproute/xds/configurer.go @@ -21,27 +21,38 @@ type RoutesConfigurer struct { func (c RoutesConfigurer) Configure(virtualHost *envoy_route.VirtualHost) error { matches := c.routeMatch(c.Matches) - var envoyRoutes []*envoy_route.Route for _, match := range matches { - envoyRoutes = append(envoyRoutes, &envoy_route.Route{ - Match: match, + r := &envoy_route.Route{ + Match: match.routeMatch, Action: &envoy_route.Route_Route{ Route: c.routeAction(c.Clusters), }, TypedPerFilterConfig: map[string]*anypb.Any{}, - }) - } + } + + // We pass the information about whether this match was created from + // a prefix match along to the filters because it's no longer + // possible to know for sure with just an envoy_route.Route + for _, filter := range c.Filters { + routeFilter(filter, r, match.prefixMatch) + } - for _, envoyRoute := range envoyRoutes { - applyRouteFilters(c.Filters, envoyRoute) + virtualHost.Routes = append(virtualHost.Routes, r) } - virtualHost.Routes = append(virtualHost.Routes, envoyRoutes...) return nil } -func (c RoutesConfigurer) routeMatch(matches []api.Match) []*envoy_route.RouteMatch { - var allEnvoyMatches []*envoy_route.RouteMatch +type routeMatch struct { + routeMatch *envoy_route.RouteMatch + prefixMatch bool +} + +// routeMatch returns a list of RouteMatches given a list of MeshHTTPRoute matches +// Note that some MeshHTTPRoute matches result in multiple Envoy matches because +// of prefix + rewrite handling. That's why we return the wrapper type as well. +func (c RoutesConfigurer) routeMatch(matches []api.Match) []routeMatch { + var allEnvoyMatches []routeMatch for _, match := range matches { var envoyMatches []*envoy_route.RouteMatch @@ -59,14 +70,18 @@ func (c RoutesConfigurer) routeMatch(matches []api.Match) []*envoy_route.RouteMa if match.QueryParams != nil { routeQueryParamsMatch(envoyMatch, match.QueryParams) } + if match.Path != nil && match.Path.Type == api.Prefix { + allEnvoyMatches = append(allEnvoyMatches, routeMatch{envoyMatch, true}) + } else { + allEnvoyMatches = append(allEnvoyMatches, routeMatch{envoyMatch, false}) + } } - - allEnvoyMatches = append(allEnvoyMatches, envoyMatches...) } return allEnvoyMatches } +// Not every API match maps cleanly to a single envoy match func (c RoutesConfigurer) routePathMatch(match api.PathMatch) []*envoy_route.RouteMatch { switch match.Type { case api.Exact: @@ -204,9 +219,3 @@ func (c RoutesConfigurer) routeAction(clusters []envoy_common.Cluster) *envoy_ro } return routeAction } - -func applyRouteFilters(filters []api.Filter, route *envoy_route.Route) { - for _, filter := range filters { - routeFilter(filter, route) - } -} diff --git a/pkg/plugins/policies/meshhttproute/xds/filters.go b/pkg/plugins/policies/meshhttproute/xds/filters.go index d72146c5f2e9..b8b4e8c4ed72 100644 --- a/pkg/plugins/policies/meshhttproute/xds/filters.go +++ b/pkg/plugins/policies/meshhttproute/xds/filters.go @@ -6,13 +6,14 @@ import ( envoy_config_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" envoy_route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + envoy_type_matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" api "github.com/kumahq/kuma/pkg/plugins/policies/meshhttproute/api/v1alpha1" "github.com/kumahq/kuma/pkg/util/pointer" util_proto "github.com/kumahq/kuma/pkg/util/proto" ) -func routeFilter(filter api.Filter, route *envoy_route.Route) { +func routeFilter(filter api.Filter, route *envoy_route.Route, matchesPrefix bool) { switch filter.Type { case api.RequestHeaderModifierType: requestHeaderModifier(*filter.RequestHeaderModifier, route) @@ -20,6 +21,8 @@ func routeFilter(filter api.Filter, route *envoy_route.Route) { responseHeaderModifier(*filter.ResponseHeaderModifier, route) case api.RequestRedirectType: requestRedirect(*filter.RequestRedirect, route) + case api.URLRewriteType: + urlRewrite(*filter.URLRewrite, route, matchesPrefix) } } @@ -105,3 +108,47 @@ func requestRedirect(redirect api.RequestRedirect, envoyRoute *envoy_route.Route Redirect: envoyRedirect, } } + +func urlRewrite(rewrite api.URLRewrite, envoyRoute *envoy_route.Route, withPrefixMatch bool) { + action := &envoy_route.RouteAction{} + if rewrite.Hostname != nil { + action.HostRewriteSpecifier = &envoy_route.RouteAction_HostRewriteLiteral{ + HostRewriteLiteral: string(*rewrite.Hostname), + } + } + if rewrite.Path != nil { + switch rewrite.Path.Type { + case api.ReplaceFullPathType: + action.RegexRewrite = &envoy_type_matcher.RegexMatchAndSubstitute{ + Pattern: &envoy_type_matcher.RegexMatcher{ + EngineType: &envoy_type_matcher.RegexMatcher_GoogleRe2{ + GoogleRe2: &envoy_type_matcher.RegexMatcher_GoogleRE2{}, + }, + Regex: `.*`, + }, + Substitution: *rewrite.Path.ReplaceFullPath, + } + case api.ReplacePrefixMatchType: + if withPrefixMatch { + if envoyRoute.Match.GetPath() != "" { + // We have the "exact /prefix" match case + action.RegexRewrite = &envoy_type_matcher.RegexMatchAndSubstitute{ + Pattern: &envoy_type_matcher.RegexMatcher{ + EngineType: &envoy_type_matcher.RegexMatcher_GoogleRe2{ + GoogleRe2: &envoy_type_matcher.RegexMatcher_GoogleRE2{}, + }, + Regex: `.*`, + }, + Substitution: *rewrite.Path.ReplacePrefixMatch, + } + } else if envoyRoute.Match.GetPrefix() != "" { + // We have the "prefix /prefix/" match case + action.PrefixRewrite = *rewrite.Path.ReplacePrefixMatch + } + } + } + } + envoyRoute.Action = &envoy_route.Route_Route{ + Route: action, + } +}