diff --git a/pkg/plugins/policies/meshhttproute/xds/configurer.go b/pkg/plugins/policies/meshhttproute/xds/configurer.go index f620382e8095..d91485c41763 100644 --- a/pkg/plugins/policies/meshhttproute/xds/configurer.go +++ b/pkg/plugins/policies/meshhttproute/xds/configurer.go @@ -5,15 +5,14 @@ import ( 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" - "google.golang.org/protobuf/types/known/anypb" common_api "github.com/kumahq/kuma/api/common/v1alpha1" api "github.com/kumahq/kuma/pkg/plugins/policies/meshhttproute/api/v1alpha1" "github.com/kumahq/kuma/pkg/plugins/policies/meshhttproute/xds/filters" - "github.com/kumahq/kuma/pkg/plugins/runtime/gateway/route" util_proto "github.com/kumahq/kuma/pkg/util/proto" "github.com/kumahq/kuma/pkg/xds/cache/sha256" envoy_common "github.com/kumahq/kuma/pkg/xds/envoy" + envoy_routes "github.com/kumahq/kuma/pkg/xds/envoy/routes" ) type RoutesConfigurer struct { @@ -32,17 +31,14 @@ func (c RoutesConfigurer) Configure(virtualHost *envoy_route.VirtualHost) error } for _, match := range matches { - rb := &route.RouteBuilder{} - - rb.Configure(route.RouteMustConfigureFunc(func(envoyRoute *envoy_route.Route) { - // todo: create configurers for Match and Action - envoyRoute.Match = match.routeMatch - envoyRoute.Action = &envoy_route.Route_Route{ - Route: c.routeAction(c.Split), - } - envoyRoute.TypedPerFilterConfig = map[string]*anypb.Any{} - envoyRoute.Name = h - })) + rb := envoy_routes.NewRouteBuilder(envoy_common.APIV3, h). + Configure(envoy_routes.RouteMustConfigureFunc(func(envoyRoute *envoy_route.Route) { + // todo: create configurers for Match and Action + envoyRoute.Match = match.routeMatch + envoyRoute.Action = &envoy_route.Route_Route{ + Route: c.routeAction(c.Split), + } + })) // We pass the information about whether this match was created from // a prefix match along to the filters because it's no longer diff --git a/pkg/plugins/policies/meshloadbalancingstrategy/plugin/xds/hashpolicyconfigurer_test.go b/pkg/plugins/policies/meshloadbalancingstrategy/plugin/xds/hashpolicyconfigurer_test.go index 2b8c6be5ecc3..ea0adc1ab700 100644 --- a/pkg/plugins/policies/meshloadbalancingstrategy/plugin/xds/hashpolicyconfigurer_test.go +++ b/pkg/plugins/policies/meshloadbalancingstrategy/plugin/xds/hashpolicyconfigurer_test.go @@ -7,10 +7,11 @@ import ( api "github.com/kumahq/kuma/pkg/plugins/policies/meshloadbalancingstrategy/api/v1alpha1" "github.com/kumahq/kuma/pkg/plugins/policies/meshloadbalancingstrategy/plugin/xds" - "github.com/kumahq/kuma/pkg/plugins/runtime/gateway/route" "github.com/kumahq/kuma/pkg/test" "github.com/kumahq/kuma/pkg/util/pointer" util_proto "github.com/kumahq/kuma/pkg/util/proto" + envoy_common "github.com/kumahq/kuma/pkg/xds/envoy" + envoy_routes "github.com/kumahq/kuma/pkg/xds/envoy/routes" ) var _ = Describe("HashPolicyConfigurer", func() { @@ -25,11 +26,13 @@ var _ = Describe("HashPolicyConfigurer", func() { configurer := &xds.HashPolicyConfigurer{ HashPolicies: given.hashPolicies, } - rb, err := (&route.RouteBuilder{}).Configure(route.RouteMustConfigureFunc(func(envoyRoute *envoy_route.Route) { - envoyRoute.Action = &envoy_route.Route_Route{ - Route: &envoy_route.RouteAction{}, - } - })).Build() + rb, err := envoy_routes.NewRouteBuilder(envoy_common.APIV3, envoy_common.AnonymousResource). + Configure( + envoy_routes.RouteMustConfigureFunc(func(envoyRoute *envoy_route.Route) { + envoyRoute.Action = &envoy_route.Route_Route{ + Route: &envoy_route.RouteAction{}, + } + })).Build() Expect(err).ToNot(HaveOccurred()) // when diff --git a/pkg/plugins/policies/meshratelimit/plugin/xds/configurer.go b/pkg/plugins/policies/meshratelimit/plugin/xds/configurer.go index 1f84ea15ae99..26408df8c27f 100644 --- a/pkg/plugins/policies/meshratelimit/plugin/xds/configurer.go +++ b/pkg/plugins/policies/meshratelimit/plugin/xds/configurer.go @@ -17,18 +17,18 @@ import ( "github.com/kumahq/kuma/pkg/util/pointer" "github.com/kumahq/kuma/pkg/util/proto" listeners_v3 "github.com/kumahq/kuma/pkg/xds/envoy/listeners/v3" - rate_limit "github.com/kumahq/kuma/pkg/xds/envoy/routes/v3" + envoy_routes_v3 "github.com/kumahq/kuma/pkg/xds/envoy/routes/v3" ) -func RateLimitConfigurationFromPolicy(rl *api.LocalHTTP) *rate_limit.RateLimitConfiguration { +func RateLimitConfigurationFromPolicy(rl *api.LocalHTTP) *envoy_routes_v3.RateLimitConfiguration { if pointer.Deref(rl.Disabled) || rl.RequestRate == nil { return nil } - onRateLimit := &rate_limit.OnRateLimit{} + onRateLimit := &envoy_routes_v3.OnRateLimit{} if rl.OnRateLimit != nil { for _, h := range pointer.Deref(rl.OnRateLimit.Headers).Add { - onRateLimit.Headers = append(onRateLimit.Headers, &rate_limit.Headers{ + onRateLimit.Headers = append(onRateLimit.Headers, &envoy_routes_v3.Headers{ Key: string(h.Name), Value: string(h.Value), Append: true, @@ -36,7 +36,7 @@ func RateLimitConfigurationFromPolicy(rl *api.LocalHTTP) *rate_limit.RateLimitCo } for _, header := range pointer.Deref(rl.OnRateLimit.Headers).Set { for _, val := range strings.Split(string(header.Value), ",") { - onRateLimit.Headers = append(onRateLimit.Headers, &rate_limit.Headers{ + onRateLimit.Headers = append(onRateLimit.Headers, &envoy_routes_v3.Headers{ Key: string(header.Name), Value: val, Append: false, @@ -46,7 +46,7 @@ func RateLimitConfigurationFromPolicy(rl *api.LocalHTTP) *rate_limit.RateLimitCo onRateLimit.Status = pointer.Deref(rl.OnRateLimit.Status) } - return &rate_limit.RateLimitConfiguration{ + return &envoy_routes_v3.RateLimitConfiguration{ Interval: rl.RequestRate.Interval.Duration, Requests: rl.RequestRate.Num, OnRateLimit: onRateLimit, @@ -82,7 +82,7 @@ func (c *Configurer) ConfigureRoute(route *envoy_route.RouteConfiguration) error return nil } - rateLimit, err := rate_limit.NewRateLimitConfiguration(rlConf) + rateLimit, err := envoy_routes_v3.NewRateLimitConfiguration(rlConf) if err != nil { return err } @@ -104,7 +104,7 @@ func (c *Configurer) configureHttpListener(filterChain *envoy_listener.FilterCha return nil } - rateLimit, err := rate_limit.NewRateLimitConfiguration(rlConf) + rateLimit, err := envoy_routes_v3.NewRateLimitConfiguration(rlConf) if err != nil { return err } diff --git a/pkg/plugins/runtime/gateway/route/builders.go b/pkg/plugins/runtime/gateway/route/builders.go deleted file mode 100644 index ecdd800addf7..000000000000 --- a/pkg/plugins/runtime/gateway/route/builders.go +++ /dev/null @@ -1,54 +0,0 @@ -package route - -import ( - envoy_config_route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" - - "github.com/kumahq/kuma/pkg/xds/envoy" -) - -type RouteConfigurer interface { - Configure(*envoy_config_route.Route) error -} - -type RouteBuilder struct { - configurers []RouteConfigurer -} - -func (r *RouteBuilder) Configure(opts ...RouteConfigurer) *RouteBuilder { - r.configurers = append(r.configurers, opts...) - return r -} - -func (r *RouteBuilder) Build() (envoy.NamedResource, error) { - route := &envoy_config_route.Route{ - Match: &envoy_config_route.RouteMatch{}, - } - - for _, c := range r.configurers { - if err := c.Configure(route); err != nil { - return nil, err - } - } - - return route, nil -} - -type RouteConfigureFunc func(*envoy_config_route.Route) error - -func (f RouteConfigureFunc) Configure(r *envoy_config_route.Route) error { - if f != nil { - return f(r) - } - - return nil -} - -type RouteMustConfigureFunc func(*envoy_config_route.Route) - -func (f RouteMustConfigureFunc) Configure(r *envoy_config_route.Route) error { - if f != nil { - f(r) - } - - return nil -} diff --git a/pkg/plugins/runtime/gateway/route/configurers.go b/pkg/plugins/runtime/gateway/route/configurers.go index f11a5f9f93eb..2c9fce7f04a8 100644 --- a/pkg/plugins/runtime/gateway/route/configurers.go +++ b/pkg/plugins/runtime/gateway/route/configurers.go @@ -1,300 +1,31 @@ package route import ( - "net/http" - "time" - envoy_config_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" envoy_config_route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" envoy_type_matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" "github.com/pkg/errors" "golang.org/x/exp/maps" "golang.org/x/exp/slices" - "google.golang.org/protobuf/types/known/anypb" mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1" core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" core_xds "github.com/kumahq/kuma/pkg/core/xds" util_proto "github.com/kumahq/kuma/pkg/util/proto" envoy_listeners "github.com/kumahq/kuma/pkg/xds/envoy/listeners/v3" - envoy_routes "github.com/kumahq/kuma/pkg/xds/envoy/routes/v3" + envoy_routes "github.com/kumahq/kuma/pkg/xds/envoy/routes" "github.com/kumahq/kuma/pkg/xds/envoy/tags" - envoy_virtual_hosts "github.com/kumahq/kuma/pkg/xds/envoy/virtualhosts" ) -// RouteMatchExactPath updates the route to match the exact path. This -// replaces any previous path match specification. -func RouteMatchExactPath(path string) RouteConfigurer { - if path == "" { - return RouteConfigureFunc(nil) - } - - return RouteMustConfigureFunc(func(r *envoy_config_route.Route) { - r.Match.PathSpecifier = &envoy_config_route.RouteMatch_Path{ - Path: path, - } - }) -} - -// RouteMatchPrefixPath updates the route to match the given path -// prefix. This is a byte-wise prefix, so it just checks that the request -// path begins with the given string. This replaces any previous path match -// specification. -func RouteMatchPrefixPath(prefix string) RouteConfigurer { - if prefix == "" { - return RouteConfigureFunc(nil) - } - - return RouteMustConfigureFunc(func(r *envoy_config_route.Route) { - r.Match.PathSpecifier = &envoy_config_route.RouteMatch_Prefix{ - Prefix: prefix, - } - }) -} - -// RouteMatchRegexPath updates the route to match the path using the -// given regex. This replaces any previous path match specification. -func RouteMatchRegexPath(regex string) RouteConfigurer { - if regex == "" { - return RouteConfigureFunc(nil) - } - - return RouteMustConfigureFunc(func(r *envoy_config_route.Route) { - r.Match.PathSpecifier = &envoy_config_route.RouteMatch_SafeRegex{ - SafeRegex: &envoy_type_matcher.RegexMatcher{Regex: regex}, - } - }) -} - -// RouteMatchExactHeader appends an exact match for the value of the named HTTP request header. -func RouteMatchExactHeader(name string, value string) RouteConfigurer { - if name == "" || value == "" { - return RouteConfigureFunc(nil) - } - - return RouteMustConfigureFunc(func(r *envoy_config_route.Route) { - matcher := envoy_type_matcher.StringMatcher{ - MatchPattern: &envoy_type_matcher.StringMatcher_Exact{ - Exact: value, - }, - } - r.Match.Headers = append(r.Match.Headers, - &envoy_config_route.HeaderMatcher{ - Name: name, - HeaderMatchSpecifier: &envoy_config_route.HeaderMatcher_StringMatch{ - StringMatch: &matcher, - }, - }, - ) - }) -} - -// RouteMatchRegexHeader appends a regex match for the value of the named HTTP request header. -func RouteMatchRegexHeader(name string, regex string) RouteConfigurer { - if name == "" || regex == "" { - return RouteConfigureFunc(nil) - } - - return RouteMustConfigureFunc(func(r *envoy_config_route.Route) { - r.Match.Headers = append(r.Match.Headers, - &envoy_config_route.HeaderMatcher{ - Name: name, - HeaderMatchSpecifier: &envoy_config_route.HeaderMatcher_StringMatch{ - StringMatch: &envoy_type_matcher.StringMatcher{ - MatchPattern: &envoy_type_matcher.StringMatcher_SafeRegex{ - SafeRegex: &envoy_type_matcher.RegexMatcher{ - Regex: regex, - }, - }, - }, - }, - }, - ) - }) -} - -// RouteMatchPresentHeader appends a present match for the names HTTP request header (presentMatch makes absent) -func RouteMatchPresentHeader(name string, presentMatch bool) RouteConfigurer { - if name == "" { - return RouteConfigureFunc(nil) - } - - return RouteMustConfigureFunc(func(r *envoy_config_route.Route) { - r.Match.Headers = append(r.Match.Headers, - &envoy_config_route.HeaderMatcher{ - Name: name, - HeaderMatchSpecifier: &envoy_config_route.HeaderMatcher_PresentMatch{ - PresentMatch: presentMatch, - }, - }, - ) - }) -} - -// RouteMatchExactQuery appends an exact match for the value of the named query parameter. -func RouteMatchExactQuery(name string, value string) RouteConfigurer { - if name == "" || value == "" { - return RouteConfigureFunc(nil) - } - - return RouteMustConfigureFunc(func(r *envoy_config_route.Route) { - matcher := envoy_type_matcher.StringMatcher{ - MatchPattern: &envoy_type_matcher.StringMatcher_Exact{ - Exact: value, - }, - } - - r.Match.QueryParameters = append(r.Match.QueryParameters, - &envoy_config_route.QueryParameterMatcher{ - Name: name, - QueryParameterMatchSpecifier: &envoy_config_route.QueryParameterMatcher_StringMatch{ - StringMatch: &matcher, - }, - }, - ) - }) -} - -// RouteMatchRegexQuery appends a regex match for the value of the named query parameter. -func RouteMatchRegexQuery(name string, regex string) RouteConfigurer { - if name == "" || regex == "" { - return RouteConfigureFunc(nil) - } - - return RouteMustConfigureFunc(func(r *envoy_config_route.Route) { - matcher := envoy_type_matcher.StringMatcher{ - MatchPattern: &envoy_type_matcher.StringMatcher_SafeRegex{ - SafeRegex: &envoy_type_matcher.RegexMatcher{Regex: regex}, - }, - } - - r.Match.QueryParameters = append(r.Match.QueryParameters, - &envoy_config_route.QueryParameterMatcher{ - Name: name, - QueryParameterMatchSpecifier: &envoy_config_route.QueryParameterMatcher_StringMatch{ - StringMatch: &matcher, - }, - }, - ) - }) -} - -func RouteAppendHeader(name string, value string) *envoy_config_core.HeaderValueOption { - return &envoy_config_core.HeaderValueOption{ - AppendAction: envoy_config_core.HeaderValueOption_APPEND_IF_EXISTS_OR_ADD, - Header: &envoy_config_core.HeaderValue{ - Key: http.CanonicalHeaderKey(name), - Value: value, - }, - } -} - -func RouteReplaceHeader(name string, value string) *envoy_config_core.HeaderValueOption { - return &envoy_config_core.HeaderValueOption{ - AppendAction: envoy_config_core.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD, - Header: &envoy_config_core.HeaderValue{ - Key: http.CanonicalHeaderKey(name), - Value: value, - }, - } -} - -// RouteAddRequestHeader alters the given request header value. -func RouteAddRequestHeader(option *envoy_config_core.HeaderValueOption) RouteConfigurer { - if option == nil { - return RouteConfigureFunc(nil) - } - - return RouteMustConfigureFunc(func(r *envoy_config_route.Route) { - r.RequestHeadersToAdd = append(r.RequestHeadersToAdd, option) - }) -} - -// RouteAddResponseHeader alters the given response header value. -func RouteAddResponseHeader(option *envoy_config_core.HeaderValueOption) RouteConfigurer { - if option == nil { - return RouteConfigureFunc(nil) - } - - return RouteMustConfigureFunc(func(r *envoy_config_route.Route) { - r.ResponseHeadersToAdd = append(r.ResponseHeadersToAdd, option) - }) -} - -// RouteReplaceHostHeader replaces the Host header on the forwarded -// request. It is an error to rewrite the header if the route is not -// forwarding. The route action must be configured beforehand. -func RouteReplaceHostHeader(host string) RouteConfigurer { - if host == "" { - return RouteConfigureFunc(nil) - } - - return RouteConfigureFunc(func(r *envoy_config_route.Route) error { - if r.GetAction() == nil { - return errors.New("cannot configure the Host header before the route action") - } - - if action := r.GetRoute(); action != nil { - action.HostRewriteSpecifier = &envoy_config_route.RouteAction_HostRewriteLiteral{ - HostRewriteLiteral: host, - } - } - - return nil - }) -} - -func RouteSetRewriteHostToBackendHostname(value bool) RouteConfigurer { - if !value { - return RouteConfigureFunc(nil) - } - - return RouteConfigureFunc(func(r *envoy_config_route.Route) error { - if r.GetAction() == nil { - return errors.New("cannot set the 'auto_host_rewrite' before the route action") - } - - if action := r.GetRoute(); action != nil { - action.HostRewriteSpecifier = &envoy_config_route.RouteAction_AutoHostRewrite{ - AutoHostRewrite: util_proto.Bool(value), - } - } - - return nil - }) -} - -// RouteDeleteRequestHeader deletes the given header from the HTTP request. -func RouteDeleteRequestHeader(name string) RouteConfigurer { - if name == "" { - return RouteConfigureFunc(nil) - } - - return RouteMustConfigureFunc(func(r *envoy_config_route.Route) { - r.RequestHeadersToRemove = append(r.RequestHeadersToRemove, name) - }) -} - -// RouteDeleteResponseHeader deletes the given header from the HTTP response. -func RouteDeleteResponseHeader(name string) RouteConfigurer { - if name == "" { - return RouteConfigureFunc(nil) - } - - return RouteMustConfigureFunc(func(r *envoy_config_route.Route) { - r.ResponseHeadersToRemove = append(r.ResponseHeadersToRemove, name) - }) -} - // RouteMirror enables traffic mirroring on the route. It is an error to enable // mirroring if the route is not forwarding. The route action must be configured // beforehand. -func RouteMirror(percent float64, destination Destination) RouteConfigurer { +func RouteMirror(percent float64, destination Destination) envoy_routes.RouteConfigurer { if percent <= 0.0 { - return RouteConfigureFunc(nil) + return envoy_routes.RouteConfigureFunc(nil) } - return RouteConfigureFunc(func(r *envoy_config_route.Route) error { + return envoy_routes.RouteConfigureFunc(func(r *envoy_config_route.Route) error { if r.GetAction() == nil { return errors.New("cannot configure mirroring before the route action") } @@ -320,35 +51,14 @@ func RouteMirror(percent float64, destination Destination) RouteConfigurer { }) } -// RoutePerFilterConfig sets an optional per-filter configuration message -// for this route. filterName is the name of the filter that should receive -// the configuration that is specified in filterConfig -func RoutePerFilterConfig(filterName string, filterConfig *anypb.Any) RouteConfigurer { - return RouteConfigureFunc(func(r *envoy_config_route.Route) error { - if r.GetTypedPerFilterConfig() == nil { - r.TypedPerFilterConfig = map[string]*anypb.Any{} - } - - m := r.GetTypedPerFilterConfig() - - if _, ok := m[filterName]; ok { - return errors.Errorf("duplicate %q per-filter config for %s", - filterConfig.GetTypeUrl(), filterName) - } - - m[filterName] = filterConfig - return nil - }) -} - // RouteActionRedirect configures the route to automatically response // with an HTTP redirection. This replaces any previous action specification. -func RouteActionRedirect(redirect *Redirection, port uint32) RouteConfigurer { +func RouteActionRedirect(redirect *Redirection, port uint32) envoy_routes.RouteConfigurer { if redirect == nil { - return RouteConfigureFunc(nil) + return envoy_routes.RouteConfigureFunc(nil) } - return RouteConfigureFunc(func(r *envoy_config_route.Route) error { + return envoy_routes.RouteConfigureFunc(func(r *envoy_config_route.Route) error { envoyRedirect := &envoy_config_route.RedirectAction{ StripQuery: redirect.StripQuery, } @@ -407,12 +117,12 @@ func RouteActionRedirect(redirect *Redirection, port uint32) RouteConfigurer { }) } -func RouteRewrite(rewrite *Rewrite) RouteConfigurer { +func RouteRewrite(rewrite *Rewrite) envoy_routes.RouteConfigurer { if rewrite == nil { - return RouteConfigureFunc(nil) + return envoy_routes.RouteConfigureFunc(nil) } - return RouteConfigureFunc(func(r *envoy_config_route.Route) error { + return envoy_routes.RouteConfigureFunc(func(r *envoy_config_route.Route) error { if r.GetAction() == nil { return errors.New("cannot configure rewrite before the route action") } @@ -443,12 +153,12 @@ func RouteRewrite(rewrite *Rewrite) RouteConfigurer { // RouteActionForward configures the route to forward traffic to the // given destinations, with the appropriate weights. This replaces any // previous action specification. -func RouteActionForward(mesh *core_mesh.MeshResource, endpoints core_xds.EndpointMap, proxyTags mesh_proto.MultiValueTagSet, destinations []Destination) RouteConfigurer { +func RouteActionForward(mesh *core_mesh.MeshResource, endpoints core_xds.EndpointMap, proxyTags mesh_proto.MultiValueTagSet, destinations []Destination) envoy_routes.RouteConfigurer { if len(destinations) == 0 { - return RouteConfigureFunc(nil) + return envoy_routes.RouteConfigureFunc(nil) } - return RouteConfigureFunc(func(r *envoy_config_route.Route) error { + return envoy_routes.RouteConfigureFunc(func(r *envoy_config_route.Route) error { byName := map[string]Destination{} for _, d := range destinations { @@ -498,114 +208,3 @@ func RouteActionForward(mesh *core_mesh.MeshResource, endpoints core_xds.Endpoin return nil }) } - -// RouteActionRetryDefault initializes the retry policy with defaults appropriate for the protocol. -func RouteActionRetryDefault(protocol core_mesh.Protocol) RouteConfigurer { - // The retry policy only supports HTTP and GRPC. - switch protocol { - case core_mesh.ProtocolHTTP, core_mesh.ProtocolHTTP2, core_mesh.ProtocolGRPC: - default: - return RouteConfigureFunc(nil) - } - - return RouteConfigureFunc(func(r *envoy_config_route.Route) error { - route := r.GetRoute() - if route == nil { - return nil - } - - p := &envoy_config_route.RetryPolicy{} - - switch protocol { - case core_mesh.ProtocolHTTP, core_mesh.ProtocolHTTP2: - p.RetryOn = envoy_routes.HttpRetryOnDefault - case core_mesh.ProtocolGRPC: - p.RetryOn = envoy_routes.GrpcRetryOnDefault - } - - route.RetryPolicy = p - return nil - }) -} - -func RouteActionRetry(retry *core_mesh.RetryResource, protocol core_mesh.Protocol) RouteConfigurer { - // The retry policy only supports HTTP and GRPC. - switch protocol { - case core_mesh.ProtocolHTTP, core_mesh.ProtocolHTTP2, core_mesh.ProtocolGRPC: - default: - return RouteConfigureFunc(nil) - } - - return RouteConfigureFunc(func(r *envoy_config_route.Route) error { - route := r.GetRoute() - route.RetryPolicy = envoy_routes.RetryConfig(retry, protocol) - return nil - }) -} - -// RouteActionRequestTimeout sets the total timeout for an upstream request. -func RouteActionRequestTimeout(timeout time.Duration) RouteConfigurer { - if timeout == 0 { - return RouteConfigureFunc(nil) - } - - return RouteConfigureFunc(func(r *envoy_config_route.Route) error { - if p := r.GetRoute(); p != nil { - p.Timeout = util_proto.Duration(timeout) - } - - return nil - }) -} - -func RouteActionIdleTimeout(timeout time.Duration) RouteConfigurer { - return RouteConfigureFunc(func(r *envoy_config_route.Route) error { - if p := r.GetRoute(); p != nil { - p.IdleTimeout = util_proto.Duration(timeout) - } - - return nil - }) -} - -// RouteActionDirectResponse sets the direct response for a route -func RouteActionDirectResponse(status uint32, respStr string) RouteConfigurer { - return RouteConfigureFunc(func(r *envoy_config_route.Route) error { - r.Action = &envoy_config_route.Route_DirectResponse{ - DirectResponse: &envoy_config_route.DirectResponseAction{ - Status: status, - Body: &envoy_config_core.DataSource{ - Specifier: &envoy_config_core.DataSource_InlineString{ - InlineString: respStr, - }, - }, - }, - } - return nil - }) -} - -// VirtualHostRoute creates an option to add the route builder to a -// virtual host. On execution, the builder will build the route and append -// it to the virtual host. Since Envoy evaluates route matches in order, -// route builders should be configured on virtual hosts in the intended -// match order. -func VirtualHostRoute(route *RouteBuilder) envoy_virtual_hosts.VirtualHostBuilderOpt { - return envoy_virtual_hosts.AddVirtualHostConfigurer( - envoy_virtual_hosts.VirtualHostConfigureFunc(func(vh *envoy_config_route.VirtualHost) error { - resource, err := route.Build() - if err != nil { - return err - } - - routeProto, ok := resource.(*envoy_config_route.Route) - if !ok { - return errors.Errorf("attempt to attach %T as type %q", - resource, "envoy_config_route.Route") - } - - vh.Routes = append(vh.Routes, routeProto) - return nil - }), - ) -} diff --git a/pkg/plugins/runtime/gateway/route_table_generator.go b/pkg/plugins/runtime/gateway/route_table_generator.go index 63554c9e1543..314c6ed7ae0d 100644 --- a/pkg/plugins/runtime/gateway/route_table_generator.go +++ b/pkg/plugins/runtime/gateway/route_table_generator.go @@ -10,6 +10,8 @@ import ( "github.com/kumahq/kuma/pkg/plugins/runtime/gateway/match" "github.com/kumahq/kuma/pkg/plugins/runtime/gateway/route" xds_context "github.com/kumahq/kuma/pkg/xds/context" + envoy_common "github.com/kumahq/kuma/pkg/xds/envoy" + envoy_routes "github.com/kumahq/kuma/pkg/xds/envoy/routes" v3 "github.com/kumahq/kuma/pkg/xds/envoy/routes/v3" envoy_virtual_hosts "github.com/kumahq/kuma/pkg/xds/envoy/virtualhosts" ) @@ -39,11 +41,12 @@ func GenerateVirtualHost( } if len(routes) == 0 { - routeBuilder := route.RouteBuilder{} - - routeBuilder.Configure(route.RouteMatchPrefixPath("/")) - routeBuilder.Configure(route.RouteActionDirectResponse(http.StatusNotFound, emptyGatewayMsg)) - vh.Configure(route.VirtualHostRoute(&routeBuilder)) + routeBuilder := envoy_routes.NewRouteBuilder(envoy_common.APIV3, envoy_common.AnonymousResource). + Configure( + envoy_routes.RouteMatchPrefixPath("/"), + envoy_routes.RouteActionDirectResponse(http.StatusNotFound, emptyGatewayMsg), + ) + vh.Configure(envoy_routes.VirtualHostRoute(routeBuilder)) return vh, nil } @@ -56,18 +59,18 @@ func GenerateVirtualHost( sort.Sort(route.Sorter(routes)) for _, e := range routes { - routeBuilder := route.RouteBuilder{} - routeBuilder.Configure( - route.RouteMatchExactPath(e.Match.ExactPath), - route.RouteMatchPrefixPath(e.Match.PrefixPath), - route.RouteMatchRegexPath(e.Match.RegexPath), - route.RouteMatchExactHeader(":method", e.Match.Method), - - route.RouteActionRedirect(e.Action.Redirect, info.Listener.Port), - route.RouteActionForward(ctx.Mesh.Resource, info.OutboundEndpoints, info.Proxy.Dataplane.Spec.TagSet(), e.Action.Forward), - route.RouteSetRewriteHostToBackendHostname(e.Rewrite != nil && e.Rewrite.HostToBackendHostname), - route.RouteActionIdleTimeout(DefaultStreamIdleTimeout), - ) + routeBuilder := envoy_routes.NewRouteBuilder(envoy_common.APIV3, envoy_common.AnonymousResource). + Configure( + envoy_routes.RouteMatchExactPath(e.Match.ExactPath), + envoy_routes.RouteMatchPrefixPath(e.Match.PrefixPath), + envoy_routes.RouteMatchRegexPath(e.Match.RegexPath), + envoy_routes.RouteMatchExactHeader(":method", e.Match.Method), + + route.RouteActionRedirect(e.Action.Redirect, info.Listener.Port), + route.RouteActionForward(ctx.Mesh.Resource, info.OutboundEndpoints, info.Proxy.Dataplane.Spec.TagSet(), e.Action.Forward), + envoy_routes.RouteSetRewriteHostToBackendHostname(e.Rewrite != nil && e.Rewrite.HostToBackendHostname), + envoy_routes.RouteActionIdleTimeout(DefaultStreamIdleTimeout), + ) // Generate a retry policy for this route, if there is one. routeBuilder.Configure( @@ -80,7 +83,7 @@ func GenerateVirtualHost( if t := match.BestConnectionPolicyForDestination(e.Action.Forward, core_mesh.TimeoutType); t != nil { timeout := t.(*core_mesh.TimeoutResource) routeBuilder.Configure( - route.RouteActionRequestTimeout(timeout.Spec.GetConf().GetHttp().GetRequestTimeout().AsDuration()), + envoy_routes.RouteActionRequestTimeout(timeout.Spec.GetConf().GetHttp().GetRequestTimeout().AsDuration()), ) } @@ -92,63 +95,63 @@ func GenerateVirtualHost( } routeBuilder.Configure( - route.RoutePerFilterConfig("envoy.filters.http.local_ratelimit", conf), + envoy_routes.RoutePerFilterConfig("envoy.filters.http.local_ratelimit", conf), ) } for _, m := range e.Match.ExactHeader { - routeBuilder.Configure(route.RouteMatchExactHeader(m.Key, m.Value)) + routeBuilder.Configure(envoy_routes.RouteMatchExactHeader(m.Key, m.Value)) } for _, m := range e.Match.RegexHeader { - routeBuilder.Configure(route.RouteMatchRegexHeader(m.Key, m.Value)) + routeBuilder.Configure(envoy_routes.RouteMatchRegexHeader(m.Key, m.Value)) } for _, m := range e.Match.PresentHeader { - routeBuilder.Configure(route.RouteMatchPresentHeader(m, true)) + routeBuilder.Configure(envoy_routes.RouteMatchPresentHeader(m, true)) } for _, m := range e.Match.AbsentHeader { - routeBuilder.Configure(route.RouteMatchPresentHeader(m, false)) + routeBuilder.Configure(envoy_routes.RouteMatchPresentHeader(m, false)) } for _, m := range e.Match.ExactQuery { - routeBuilder.Configure(route.RouteMatchExactQuery(m.Key, m.Value)) + routeBuilder.Configure(envoy_routes.RouteMatchExactQuery(m.Key, m.Value)) } for _, m := range e.Match.RegexQuery { - routeBuilder.Configure(route.RouteMatchRegexQuery(m.Key, m.Value)) + routeBuilder.Configure(envoy_routes.RouteMatchRegexQuery(m.Key, m.Value)) } if rq := e.RequestHeaders; rq != nil { for _, h := range e.RequestHeaders.Replace { switch h.Key { case ":authority", "Host", "host": - routeBuilder.Configure(route.RouteReplaceHostHeader(h.Value)) + routeBuilder.Configure(envoy_routes.RouteReplaceHostHeader(h.Value)) default: - routeBuilder.Configure(route.RouteAddRequestHeader(route.RouteReplaceHeader(h.Key, h.Value))) + routeBuilder.Configure(envoy_routes.RouteAddRequestHeader(envoy_routes.RouteReplaceHeader(h.Key, h.Value))) } } for _, h := range e.RequestHeaders.Append { - routeBuilder.Configure(route.RouteAddRequestHeader(route.RouteAppendHeader(h.Key, h.Value))) + routeBuilder.Configure(envoy_routes.RouteAddRequestHeader(envoy_routes.RouteAppendHeader(h.Key, h.Value))) } for _, name := range e.RequestHeaders.Delete { - routeBuilder.Configure(route.RouteDeleteRequestHeader(name)) + routeBuilder.Configure(envoy_routes.RouteDeleteRequestHeader(name)) } } if rq := e.ResponseHeaders; rq != nil { for _, h := range e.ResponseHeaders.Replace { - routeBuilder.Configure(route.RouteAddResponseHeader(route.RouteReplaceHeader(h.Key, h.Value))) + routeBuilder.Configure(envoy_routes.RouteAddResponseHeader(envoy_routes.RouteReplaceHeader(h.Key, h.Value))) } for _, h := range e.ResponseHeaders.Append { - routeBuilder.Configure(route.RouteAddResponseHeader(route.RouteAppendHeader(h.Key, h.Value))) + routeBuilder.Configure(envoy_routes.RouteAddResponseHeader(envoy_routes.RouteAppendHeader(h.Key, h.Value))) } for _, name := range e.ResponseHeaders.Delete { - routeBuilder.Configure(route.RouteDeleteResponseHeader(name)) + routeBuilder.Configure(envoy_routes.RouteDeleteResponseHeader(name)) } } @@ -160,21 +163,21 @@ func GenerateVirtualHost( routeBuilder.Configure(route.RouteRewrite(e.Rewrite)) - vh.Configure(route.VirtualHostRoute(&routeBuilder)) + vh.Configure(envoy_routes.VirtualHostRoute(routeBuilder)) } return vh, nil } // retryRouteConfigurers returns the set of route configurers needed to implement the retry policy (if there is one). -func retryRouteConfigurers(protocol core_mesh.Protocol, policy model.Resource) []route.RouteConfigurer { +func retryRouteConfigurers(protocol core_mesh.Protocol, policy model.Resource) []envoy_routes.RouteConfigurer { retry, _ := policy.(*core_mesh.RetryResource) if retry == nil { return nil } - return []route.RouteConfigurer{ - route.RouteActionRetryDefault(protocol), - route.RouteActionRetry(retry, protocol), + return []envoy_routes.RouteConfigurer{ + envoy_routes.RouteActionRetryDefault(protocol), + envoy_routes.RouteActionRetry(retry, protocol), } } diff --git a/pkg/xds/envoy/listeners/v3/static_endpoints_configurer.go b/pkg/xds/envoy/listeners/v3/static_endpoints_configurer.go index a3731ab47d58..7e35823b35c9 100644 --- a/pkg/xds/envoy/listeners/v3/static_endpoints_configurer.go +++ b/pkg/xds/envoy/listeners/v3/static_endpoints_configurer.go @@ -27,6 +27,7 @@ func (c *StaticEndpointsConfigurer) Configure(filterChain *envoy_listener.Filter Prefix: p.Path, }, }, + Name: envoy_common.AnonymousResource, Action: &envoy_route.Route_Route{ Route: &envoy_route.RouteAction{ ClusterSpecifier: &envoy_route.RouteAction_Cluster{ diff --git a/pkg/xds/envoy/routes/common_route_configuration_configurer.go b/pkg/xds/envoy/routes/common_route_configuration_configurer.go new file mode 100644 index 000000000000..af8fade922a9 --- /dev/null +++ b/pkg/xds/envoy/routes/common_route_configuration_configurer.go @@ -0,0 +1,14 @@ +package routes + +import ( + envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + + util_proto "github.com/kumahq/kuma/pkg/util/proto" +) + +type CommonRouteConfigurationConfigurer struct{} + +func (c CommonRouteConfigurationConfigurer) Configure(routeConfiguration *envoy_config_route_v3.RouteConfiguration) error { + routeConfiguration.ValidateClusters = util_proto.Bool(false) + return nil +} diff --git a/pkg/xds/envoy/routes/route_builder.go b/pkg/xds/envoy/routes/route_builder.go new file mode 100644 index 000000000000..3f49275aa207 --- /dev/null +++ b/pkg/xds/envoy/routes/route_builder.go @@ -0,0 +1,67 @@ +package routes + +import ( + envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + "google.golang.org/protobuf/types/known/anypb" + + core_xds "github.com/kumahq/kuma/pkg/core/xds" + "github.com/kumahq/kuma/pkg/xds/envoy" +) + +type RouteConfigurer interface { + Configure(*envoy_config_route_v3.Route) error +} + +type RouteBuilder struct { + apiVersion core_xds.APIVersion + configurers []RouteConfigurer + name string +} + +func NewRouteBuilder(apiVersion core_xds.APIVersion, name string) *RouteBuilder { + return &RouteBuilder{ + apiVersion: apiVersion, + name: name, + } +} + +func (r *RouteBuilder) Configure(opts ...RouteConfigurer) *RouteBuilder { + r.configurers = append(r.configurers, opts...) + return r +} + +func (r *RouteBuilder) Build() (envoy.NamedResource, error) { + route := &envoy_config_route_v3.Route{ + Match: &envoy_config_route_v3.RouteMatch{}, + Name: r.name, + TypedPerFilterConfig: map[string]*anypb.Any{}, + } + + for _, c := range r.configurers { + if err := c.Configure(route); err != nil { + return nil, err + } + } + + return route, nil +} + +type RouteConfigureFunc func(*envoy_config_route_v3.Route) error + +func (f RouteConfigureFunc) Configure(r *envoy_config_route_v3.Route) error { + if f != nil { + return f(r) + } + + return nil +} + +type RouteMustConfigureFunc func(*envoy_config_route_v3.Route) + +func (f RouteMustConfigureFunc) Configure(r *envoy_config_route_v3.Route) error { + if f != nil { + f(r) + } + + return nil +} diff --git a/pkg/xds/envoy/routes/route_configuration_builder.go b/pkg/xds/envoy/routes/route_configuration_builder.go index 13552619f117..e8888f8d9dbc 100644 --- a/pkg/xds/envoy/routes/route_configuration_builder.go +++ b/pkg/xds/envoy/routes/route_configuration_builder.go @@ -1,7 +1,7 @@ package routes import ( - envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" "github.com/pkg/errors" core_xds "github.com/kumahq/kuma/pkg/core/xds" @@ -44,7 +44,7 @@ func (b *RouteConfigurationBuilder) Configure(opts ...RouteConfigurationBuilderO func (b *RouteConfigurationBuilder) Build() (envoy.NamedResource, error) { switch b.apiVersion { case envoy.APIV3: - routeConfiguration := envoy_route_v3.RouteConfiguration{ + routeConfiguration := envoy_config_route_v3.RouteConfiguration{ Name: b.name, } for _, configurer := range b.configurers { diff --git a/pkg/xds/envoy/routes/configurers.go b/pkg/xds/envoy/routes/route_configuration_configurers.go similarity index 81% rename from pkg/xds/envoy/routes/configurers.go rename to pkg/xds/envoy/routes/route_configuration_configurers.go index 3e88901c00a1..333468e75b17 100644 --- a/pkg/xds/envoy/routes/configurers.go +++ b/pkg/xds/envoy/routes/route_configuration_configurers.go @@ -1,7 +1,7 @@ package routes import ( - envoy_route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1" v3 "github.com/kumahq/kuma/pkg/xds/envoy/routes/v3" @@ -23,12 +23,12 @@ func TagsHeader(tags mesh_proto.MultiValueTagSet) RouteConfigurationBuilderOpt { func VirtualHost(builder *envoy_virtual_hosts.VirtualHostBuilder) RouteConfigurationBuilderOpt { return AddRouteConfigurationConfigurer( - v3.RouteConfigurationConfigureFunc(func(rc *envoy_route.RouteConfiguration) error { + v3.RouteConfigurationConfigureFunc(func(rc *envoy_config_route_v3.RouteConfiguration) error { virtualHost, err := builder.Build() if err != nil { return err } - rc.VirtualHosts = append(rc.VirtualHosts, virtualHost.(*envoy_route.VirtualHost)) + rc.VirtualHosts = append(rc.VirtualHosts, virtualHost.(*envoy_config_route_v3.VirtualHost)) return nil })) } @@ -40,7 +40,7 @@ func CommonRouteConfiguration() RouteConfigurationBuilderOpt { func IgnorePortInHostMatching() RouteConfigurationBuilderOpt { return AddRouteConfigurationConfigurer( - v3.RouteConfigurationConfigureFunc(func(rc *envoy_route.RouteConfiguration) error { + v3.RouteConfigurationConfigureFunc(func(rc *envoy_config_route_v3.RouteConfiguration) error { rc.IgnorePortInHostMatching = true return nil }), diff --git a/pkg/xds/envoy/routes/route_configurers.go b/pkg/xds/envoy/routes/route_configurers.go new file mode 100644 index 000000000000..a2e502b81967 --- /dev/null +++ b/pkg/xds/envoy/routes/route_configurers.go @@ -0,0 +1,413 @@ +package routes + +import ( + "net/http" + "time" + + envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + envoy_type_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" + "github.com/pkg/errors" + "google.golang.org/protobuf/types/known/anypb" + + core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" + util_proto "github.com/kumahq/kuma/pkg/util/proto" + envoy_routes "github.com/kumahq/kuma/pkg/xds/envoy/routes/v3" + envoy_virtual_hosts "github.com/kumahq/kuma/pkg/xds/envoy/virtualhosts" +) + +// RouteMatchExactPath updates the route to match the exact path. This +// replaces any previous path match specification. +func RouteMatchExactPath(path string) RouteConfigurer { + if path == "" { + return RouteConfigureFunc(nil) + } + + return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { + r.Match.PathSpecifier = &envoy_config_route_v3.RouteMatch_Path{ + Path: path, + } + }) +} + +// RouteMatchPrefixPath updates the route to match the given path +// prefix. This is a byte-wise prefix, so it just checks that the request +// path begins with the given string. This replaces any previous path match +// specification. +func RouteMatchPrefixPath(prefix string) RouteConfigurer { + if prefix == "" { + return RouteConfigureFunc(nil) + } + + return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { + r.Match.PathSpecifier = &envoy_config_route_v3.RouteMatch_Prefix{ + Prefix: prefix, + } + }) +} + +// RouteMatchRegexPath updates the route to match the path using the +// given regex. This replaces any previous path match specification. +func RouteMatchRegexPath(regex string) RouteConfigurer { + if regex == "" { + return RouteConfigureFunc(nil) + } + + return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { + r.Match.PathSpecifier = &envoy_config_route_v3.RouteMatch_SafeRegex{ + SafeRegex: &envoy_type_matcher_v3.RegexMatcher{Regex: regex}, + } + }) +} + +// RouteMatchExactHeader appends an exact match for the value of the named HTTP request header. +func RouteMatchExactHeader(name string, value string) RouteConfigurer { + if name == "" || value == "" { + return RouteConfigureFunc(nil) + } + + return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { + matcher := envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ + Exact: value, + }, + } + r.Match.Headers = append(r.Match.Headers, + &envoy_config_route_v3.HeaderMatcher{ + Name: name, + HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &matcher, + }, + }, + ) + }) +} + +// RouteMatchRegexHeader appends a regex match for the value of the named HTTP request header. +func RouteMatchRegexHeader(name string, regex string) RouteConfigurer { + if name == "" || regex == "" { + return RouteConfigureFunc(nil) + } + + return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { + r.Match.Headers = append(r.Match.Headers, + &envoy_config_route_v3.HeaderMatcher{ + Name: name, + HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ + StringMatch: &envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_SafeRegex{ + SafeRegex: &envoy_type_matcher_v3.RegexMatcher{ + Regex: regex, + }, + }, + }, + }, + }, + ) + }) +} + +// RouteMatchPresentHeader appends a present match for the names HTTP request header (presentMatch makes absent) +func RouteMatchPresentHeader(name string, presentMatch bool) RouteConfigurer { + if name == "" { + return RouteConfigureFunc(nil) + } + + return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { + r.Match.Headers = append(r.Match.Headers, + &envoy_config_route_v3.HeaderMatcher{ + Name: name, + HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_PresentMatch{ + PresentMatch: presentMatch, + }, + }, + ) + }) +} + +// RouteMatchExactQuery appends an exact match for the value of the named query parameter. +func RouteMatchExactQuery(name string, value string) RouteConfigurer { + if name == "" || value == "" { + return RouteConfigureFunc(nil) + } + + return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { + matcher := envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ + Exact: value, + }, + } + + r.Match.QueryParameters = append(r.Match.QueryParameters, + &envoy_config_route_v3.QueryParameterMatcher{ + Name: name, + QueryParameterMatchSpecifier: &envoy_config_route_v3.QueryParameterMatcher_StringMatch{ + StringMatch: &matcher, + }, + }, + ) + }) +} + +// RouteMatchRegexQuery appends a regex match for the value of the named query parameter. +func RouteMatchRegexQuery(name string, regex string) RouteConfigurer { + if name == "" || regex == "" { + return RouteConfigureFunc(nil) + } + + return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { + matcher := envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_SafeRegex{ + SafeRegex: &envoy_type_matcher_v3.RegexMatcher{Regex: regex}, + }, + } + + r.Match.QueryParameters = append(r.Match.QueryParameters, + &envoy_config_route_v3.QueryParameterMatcher{ + Name: name, + QueryParameterMatchSpecifier: &envoy_config_route_v3.QueryParameterMatcher_StringMatch{ + StringMatch: &matcher, + }, + }, + ) + }) +} + +func RouteAppendHeader(name string, value string) *envoy_config_core_v3.HeaderValueOption { + return &envoy_config_core_v3.HeaderValueOption{ + AppendAction: envoy_config_core_v3.HeaderValueOption_APPEND_IF_EXISTS_OR_ADD, + Header: &envoy_config_core_v3.HeaderValue{ + Key: http.CanonicalHeaderKey(name), + Value: value, + }, + } +} + +func RouteReplaceHeader(name string, value string) *envoy_config_core_v3.HeaderValueOption { + return &envoy_config_core_v3.HeaderValueOption{ + AppendAction: envoy_config_core_v3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD, + Header: &envoy_config_core_v3.HeaderValue{ + Key: http.CanonicalHeaderKey(name), + Value: value, + }, + } +} + +// RouteAddRequestHeader alters the given request header value. +func RouteAddRequestHeader(option *envoy_config_core_v3.HeaderValueOption) RouteConfigurer { + if option == nil { + return RouteConfigureFunc(nil) + } + + return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { + r.RequestHeadersToAdd = append(r.RequestHeadersToAdd, option) + }) +} + +// RouteAddResponseHeader alters the given response header value. +func RouteAddResponseHeader(option *envoy_config_core_v3.HeaderValueOption) RouteConfigurer { + if option == nil { + return RouteConfigureFunc(nil) + } + + return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { + r.ResponseHeadersToAdd = append(r.ResponseHeadersToAdd, option) + }) +} + +// RouteReplaceHostHeader replaces the Host header on the forwarded +// request. It is an error to rewrite the header if the route is not +// forwarding. The route action must be configured beforehand. +func RouteReplaceHostHeader(host string) RouteConfigurer { + if host == "" { + return RouteConfigureFunc(nil) + } + + return RouteConfigureFunc(func(r *envoy_config_route_v3.Route) error { + if r.GetAction() == nil { + return errors.New("cannot configure the Host header before the route action") + } + + if action := r.GetRoute(); action != nil { + action.HostRewriteSpecifier = &envoy_config_route_v3.RouteAction_HostRewriteLiteral{ + HostRewriteLiteral: host, + } + } + + return nil + }) +} + +func RouteSetRewriteHostToBackendHostname(value bool) RouteConfigurer { + if !value { + return RouteConfigureFunc(nil) + } + + return RouteConfigureFunc(func(r *envoy_config_route_v3.Route) error { + if r.GetAction() == nil { + return errors.New("cannot set the 'auto_host_rewrite' before the route action") + } + + if action := r.GetRoute(); action != nil { + action.HostRewriteSpecifier = &envoy_config_route_v3.RouteAction_AutoHostRewrite{ + AutoHostRewrite: util_proto.Bool(value), + } + } + + return nil + }) +} + +// RouteDeleteRequestHeader deletes the given header from the HTTP request. +func RouteDeleteRequestHeader(name string) RouteConfigurer { + if name == "" { + return RouteConfigureFunc(nil) + } + + return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { + r.RequestHeadersToRemove = append(r.RequestHeadersToRemove, name) + }) +} + +// RouteDeleteResponseHeader deletes the given header from the HTTP response. +func RouteDeleteResponseHeader(name string) RouteConfigurer { + if name == "" { + return RouteConfigureFunc(nil) + } + + return RouteMustConfigureFunc(func(r *envoy_config_route_v3.Route) { + r.ResponseHeadersToRemove = append(r.ResponseHeadersToRemove, name) + }) +} + +// RoutePerFilterConfig sets an optional per-filter configuration message +// for this route. filterName is the name of the filter that should receive +// the configuration that is specified in filterConfig +func RoutePerFilterConfig(filterName string, filterConfig *anypb.Any) RouteConfigurer { + return RouteConfigureFunc(func(r *envoy_config_route_v3.Route) error { + if r.GetTypedPerFilterConfig() == nil { + r.TypedPerFilterConfig = map[string]*anypb.Any{} + } + + m := r.GetTypedPerFilterConfig() + + if _, ok := m[filterName]; ok { + return errors.Errorf("duplicate %q per-filter config for %s", + filterConfig.GetTypeUrl(), filterName) + } + + m[filterName] = filterConfig + return nil + }) +} + +// RouteActionRetryDefault initializes the retry policy with defaults appropriate for the protocol. +func RouteActionRetryDefault(protocol core_mesh.Protocol) RouteConfigurer { + // The retry policy only supports HTTP and GRPC. + switch protocol { + case core_mesh.ProtocolHTTP, core_mesh.ProtocolHTTP2, core_mesh.ProtocolGRPC: + default: + return RouteConfigureFunc(nil) + } + + return RouteConfigureFunc(func(r *envoy_config_route_v3.Route) error { + route := r.GetRoute() + if route == nil { + return nil + } + + p := &envoy_config_route_v3.RetryPolicy{} + + switch protocol { + case core_mesh.ProtocolHTTP, core_mesh.ProtocolHTTP2: + p.RetryOn = envoy_routes.HttpRetryOnDefault + case core_mesh.ProtocolGRPC: + p.RetryOn = envoy_routes.GrpcRetryOnDefault + } + + route.RetryPolicy = p + return nil + }) +} + +func RouteActionRetry(retry *core_mesh.RetryResource, protocol core_mesh.Protocol) RouteConfigurer { + // The retry policy only supports HTTP and GRPC. + switch protocol { + case core_mesh.ProtocolHTTP, core_mesh.ProtocolHTTP2, core_mesh.ProtocolGRPC: + default: + return RouteConfigureFunc(nil) + } + + return RouteConfigureFunc(func(r *envoy_config_route_v3.Route) error { + route := r.GetRoute() + route.RetryPolicy = envoy_routes.RetryConfig(retry, protocol) + return nil + }) +} + +// RouteActionRequestTimeout sets the total timeout for an upstream request. +func RouteActionRequestTimeout(timeout time.Duration) RouteConfigurer { + if timeout == 0 { + return RouteConfigureFunc(nil) + } + + return RouteConfigureFunc(func(r *envoy_config_route_v3.Route) error { + if p := r.GetRoute(); p != nil { + p.Timeout = util_proto.Duration(timeout) + } + + return nil + }) +} + +func RouteActionIdleTimeout(timeout time.Duration) RouteConfigurer { + return RouteConfigureFunc(func(r *envoy_config_route_v3.Route) error { + if p := r.GetRoute(); p != nil { + p.IdleTimeout = util_proto.Duration(timeout) + } + + return nil + }) +} + +// RouteActionDirectResponse sets the direct response for a route +func RouteActionDirectResponse(status uint32, respStr string) RouteConfigurer { + return RouteConfigureFunc(func(r *envoy_config_route_v3.Route) error { + r.Action = &envoy_config_route_v3.Route_DirectResponse{ + DirectResponse: &envoy_config_route_v3.DirectResponseAction{ + Status: status, + Body: &envoy_config_core_v3.DataSource{ + Specifier: &envoy_config_core_v3.DataSource_InlineString{ + InlineString: respStr, + }, + }, + }, + } + return nil + }) +} + +// VirtualHostRoute creates an option to add the route builder to a +// virtual host. On execution, the builder will build the route and append +// it to the virtual host. Since Envoy evaluates route matches in order, +// route builders should be configured on virtual hosts in the intended +// match order. +func VirtualHostRoute(route *RouteBuilder) envoy_virtual_hosts.VirtualHostBuilderOpt { + return envoy_virtual_hosts.AddVirtualHostConfigurer( + envoy_virtual_hosts.VirtualHostConfigureFunc(func(vh *envoy_config_route_v3.VirtualHost) error { + resource, err := route.Build() + if err != nil { + return err + } + + routeProto, ok := resource.(*envoy_config_route_v3.Route) + if !ok { + return errors.Errorf("attempt to attach %T as type %q", + resource, "envoy_config_route_v3.Route") + } + + vh.Routes = append(vh.Routes, routeProto) + return nil + }), + ) +} diff --git a/pkg/xds/envoy/routes/v3/common_route_configuration_configurer.go b/pkg/xds/envoy/routes/v3/common_route_configuration_configurer.go index 39ca8b61602e..5b0fafb2ca22 100644 --- a/pkg/xds/envoy/routes/v3/common_route_configuration_configurer.go +++ b/pkg/xds/envoy/routes/v3/common_route_configuration_configurer.go @@ -1,14 +1,14 @@ package v3 import ( - envoy_route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" util_proto "github.com/kumahq/kuma/pkg/util/proto" ) type CommonRouteConfigurationConfigurer struct{} -func (c CommonRouteConfigurationConfigurer) Configure(routeConfiguration *envoy_route.RouteConfiguration) error { +func (c CommonRouteConfigurationConfigurer) Configure(routeConfiguration *envoy_config_route_v3.RouteConfiguration) error { routeConfiguration.ValidateClusters = util_proto.Bool(false) return nil } diff --git a/pkg/xds/envoy/routes/v3/reset_tags_header_configurer.go b/pkg/xds/envoy/routes/v3/reset_tags_header_configurer.go index 654b80a42cf6..cf18aaf3f397 100644 --- a/pkg/xds/envoy/routes/v3/reset_tags_header_configurer.go +++ b/pkg/xds/envoy/routes/v3/reset_tags_header_configurer.go @@ -1,14 +1,14 @@ package v3 import ( - envoy_route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" "github.com/kumahq/kuma/pkg/xds/envoy/tags" ) type ResetTagsHeaderConfigurer struct{} -func (r *ResetTagsHeaderConfigurer) Configure(rc *envoy_route.RouteConfiguration) error { +func (r *ResetTagsHeaderConfigurer) Configure(rc *envoy_config_route_v3.RouteConfiguration) error { rc.RequestHeadersToRemove = append(rc.RequestHeadersToRemove, tags.TagsHeaderName) return nil } diff --git a/pkg/xds/envoy/routes/v3/retry_configurer.go b/pkg/xds/envoy/routes/v3/retry_configurer.go index ecbb0da6d305..acf2cd8de2ca 100644 --- a/pkg/xds/envoy/routes/v3/retry_configurer.go +++ b/pkg/xds/envoy/routes/v3/retry_configurer.go @@ -3,8 +3,8 @@ package v3 import ( "strings" - 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" + envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + envoy_type_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1" core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" @@ -21,12 +21,12 @@ const ( func genGrpcRetryPolicy( conf *mesh_proto.Retry_Conf_Grpc, -) *envoy_route.RetryPolicy { +) *envoy_config_route_v3.RetryPolicy { if conf == nil { return nil } - policy := envoy_route.RetryPolicy{ + policy := envoy_config_route_v3.RetryPolicy{ RetryOn: GrpcRetryOnDefault, PerTryTimeout: conf.PerTryTimeout, } @@ -41,7 +41,7 @@ func genGrpcRetryPolicy( } if conf.BackOff != nil { - policy.RetryBackOff = &envoy_route.RetryPolicy_RetryBackOff{ + policy.RetryBackOff = &envoy_config_route_v3.RetryPolicy_RetryBackOff{ BaseInterval: conf.BackOff.BaseInterval, MaxInterval: conf.BackOff.MaxInterval, } @@ -52,12 +52,12 @@ func genGrpcRetryPolicy( func genHttpRetryPolicy( conf *mesh_proto.Retry_Conf_Http, -) *envoy_route.RetryPolicy { +) *envoy_config_route_v3.RetryPolicy { if conf == nil { return nil } - policy := envoy_route.RetryPolicy{ + policy := envoy_config_route_v3.RetryPolicy{ RetryOn: HttpRetryOnDefault, PerTryTimeout: conf.PerTryTimeout, } @@ -67,7 +67,7 @@ func genHttpRetryPolicy( } if conf.BackOff != nil { - policy.RetryBackOff = &envoy_route.RetryPolicy_RetryBackOff{ + policy.RetryBackOff = &envoy_config_route_v3.RetryPolicy_RetryBackOff{ BaseInterval: conf.BackOff.BaseInterval, MaxInterval: conf.BackOff.MaxInterval, } @@ -88,15 +88,15 @@ func genHttpRetryPolicy( continue } - matcher := envoy_type_matcher.StringMatcher{ - MatchPattern: &envoy_type_matcher.StringMatcher_Exact{ + matcher := envoy_type_matcher_v3.StringMatcher{ + MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{ Exact: method.String(), }, } policy.RetriableRequestHeaders = append(policy.RetriableRequestHeaders, - &envoy_route.HeaderMatcher{ + &envoy_config_route_v3.HeaderMatcher{ Name: ":method", - HeaderMatchSpecifier: &envoy_route.HeaderMatcher_StringMatch{ + HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{ StringMatch: &matcher, }, InvertMatch: false, @@ -143,7 +143,7 @@ func GrpcRetryOn(conf []mesh_proto.Retry_Conf_Grpc_RetryOn) string { return strings.Join(retryOn, ",") } -func RetryConfig(retry *core_mesh.RetryResource, protocol core_mesh.Protocol) *envoy_route.RetryPolicy { +func RetryConfig(retry *core_mesh.RetryResource, protocol core_mesh.Protocol) *envoy_config_route_v3.RetryPolicy { if retry == nil { return nil } diff --git a/pkg/xds/envoy/routes/v3/configurer.go b/pkg/xds/envoy/routes/v3/route_configuration_configurer.go similarity index 59% rename from pkg/xds/envoy/routes/v3/configurer.go rename to pkg/xds/envoy/routes/v3/route_configuration_configurer.go index 8833c02d6d61..cc0288f5411c 100644 --- a/pkg/xds/envoy/routes/v3/configurer.go +++ b/pkg/xds/envoy/routes/v3/route_configuration_configurer.go @@ -1,21 +1,21 @@ package v3 import ( - envoy_route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" ) // RouteConfigurationConfigurer is responsible for configuring a single aspect of the entire Envoy RouteConfiguration, // such as VirtualHost, HTTP headers to add or remove, etc. type RouteConfigurationConfigurer interface { // Configure configures a single aspect on a given Envoy RouteConfiguration. - Configure(routeConfiguration *envoy_route.RouteConfiguration) error + Configure(routeConfiguration *envoy_config_route_v3.RouteConfiguration) error } // RouteConfigurationConfigureFunc adapts a configuration function to the // RouteConfigurationConfigurer interface. -type RouteConfigurationConfigureFunc func(rc *envoy_route.RouteConfiguration) error +type RouteConfigurationConfigureFunc func(rc *envoy_config_route_v3.RouteConfiguration) error -func (f RouteConfigurationConfigureFunc) Configure(rc *envoy_route.RouteConfiguration) error { +func (f RouteConfigurationConfigureFunc) Configure(rc *envoy_config_route_v3.RouteConfiguration) error { if f != nil { return f(rc) } @@ -25,9 +25,9 @@ func (f RouteConfigurationConfigureFunc) Configure(rc *envoy_route.RouteConfigur // RouteConfigurationMustConfigureFunc adapts a configuration function that // never fails to the RouteConfigurationConfigurer interface. -type RouteConfigurationMustConfigureFunc func(rc *envoy_route.RouteConfiguration) +type RouteConfigurationMustConfigureFunc func(rc *envoy_config_route_v3.RouteConfiguration) -func (f RouteConfigurationMustConfigureFunc) Configure(rc *envoy_route.RouteConfiguration) error { +func (f RouteConfigurationMustConfigureFunc) Configure(rc *envoy_config_route_v3.RouteConfiguration) error { if f != nil { f(rc) } diff --git a/pkg/xds/envoy/routes/v3/tags_header_configurer.go b/pkg/xds/envoy/routes/v3/tags_header_configurer.go index eb92ef7744e9..e719d8602d8c 100644 --- a/pkg/xds/envoy/routes/v3/tags_header_configurer.go +++ b/pkg/xds/envoy/routes/v3/tags_header_configurer.go @@ -1,8 +1,8 @@ package v3 import ( - envoy_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - envoy_route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1" "github.com/kumahq/kuma/pkg/xds/envoy/tags" @@ -12,12 +12,12 @@ type TagsHeaderConfigurer struct { Tags mesh_proto.MultiValueTagSet } -func (t *TagsHeaderConfigurer) Configure(rc *envoy_route.RouteConfiguration) error { +func (t *TagsHeaderConfigurer) Configure(rc *envoy_config_route_v3.RouteConfiguration) error { if len(t.Tags) == 0 { return nil } - rc.RequestHeadersToAdd = append(rc.RequestHeadersToAdd, &envoy_core.HeaderValueOption{ - Header: &envoy_core.HeaderValue{Key: tags.TagsHeaderName, Value: tags.Serialize(t.Tags)}, + rc.RequestHeadersToAdd = append(rc.RequestHeadersToAdd, &envoy_config_core_v3.HeaderValueOption{ + Header: &envoy_config_core_v3.HeaderValue{Key: tags.TagsHeaderName, Value: tags.Serialize(t.Tags)}, }) return nil } diff --git a/pkg/xds/envoy/virtualhosts/configurer.go b/pkg/xds/envoy/virtualhosts/configurer.go index 353441c08dc8..db47b6c3933f 100644 --- a/pkg/xds/envoy/virtualhosts/configurer.go +++ b/pkg/xds/envoy/virtualhosts/configurer.go @@ -75,7 +75,7 @@ func Retry(retry *core_mesh.RetryResource, protocol core_mesh.Protocol) VirtualH func Route(matchPath, newPath, cluster string, allowGetOnly bool) VirtualHostBuilderOpt { return AddVirtualHostConfigurer( - &RouteConfigurer{ + &VirtualHostRouteConfigurer{ MatchPath: matchPath, NewPath: newPath, Cluster: cluster, diff --git a/pkg/xds/envoy/virtualhosts/route_configurer.go b/pkg/xds/envoy/virtualhosts/route_configurer.go index c29865709190..34187561c810 100644 --- a/pkg/xds/envoy/virtualhosts/route_configurer.go +++ b/pkg/xds/envoy/virtualhosts/route_configurer.go @@ -7,14 +7,14 @@ import ( envoy_common "github.com/kumahq/kuma/pkg/xds/envoy" ) -type RouteConfigurer struct { +type VirtualHostRouteConfigurer struct { MatchPath string NewPath string Cluster string AllowGetOnly bool } -func (c RouteConfigurer) Configure(virtualHost *envoy_config_route_v3.VirtualHost) error { +func (c VirtualHostRouteConfigurer) Configure(virtualHost *envoy_config_route_v3.VirtualHost) error { var headersMatcher []*envoy_config_route_v3.HeaderMatcher if c.AllowGetOnly { matcher := envoy_type_matcher_v3.StringMatcher{