diff --git a/CHANGELOG.md b/CHANGELOG.md index dd44482298..bbe352d9d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -159,7 +159,6 @@ Adding a new version? You'll need three changes: version `gateway.networking.k8s.io/v1`. [#4935](https://github.com/Kong/kubernetes-ingress-controller/pull/4935) - ### Added - Added support for expression-based Kong routes for `TLSRoute`. This requires @@ -174,6 +173,10 @@ Adding a new version? You'll need three changes: [#4762](https://github.com/Kong/kubernetes-ingress-controller/pull/4762) - Support Query Parameter matching of `HTTPRoute` when expression router enabled. [#4780](https://github.com/Kong/kubernetes-ingress-controller/pull/4780) +- Support `ExtensionRef` HTTPRoute filter. It is now possibile to set a KongPlugin + reference in the `HTTPRoute`s' `ExtensionRef` filter field. + [#4838](https://github.com/Kong/kubernetes-ingress-controller/pull/4838) + [KIC Annotations reference]: https://docs.konghq.com/kubernetes-ingress-controller/latest/references/annotations/ diff --git a/internal/dataplane/parser/translate_httproute.go b/internal/dataplane/parser/translate_httproute.go index 25d4a3aaf4..2b964beb80 100644 --- a/internal/dataplane/parser/translate_httproute.go +++ b/internal/dataplane/parser/translate_httproute.go @@ -266,11 +266,16 @@ func generateKongRoutesFromHTTPRouteMatches( return filter.Type == gatewayapi.HTTPRouteFilterRequestRedirect }) - routes := getRoutesFromMatches(matches, &r, filters, tags, hasRedirectFilter) + routes, err := getRoutesFromMatches(matches, &r, filters, tags, hasRedirectFilter) + if err != nil { + return nil, err + } // if the redirect filter has not been set, we still need to set the route plugins if !hasRedirectFilter { - translators.ConvertFiltersToPlugins(&r, filters, "", tags) + if err := translators.SetRoutePlugins(&r, filters, "", tags); err != nil { + return nil, err + } routes = []kongstate.Route{r} } @@ -284,7 +289,7 @@ func getRoutesFromMatches( filters []gatewayapi.HTTPRouteFilter, tags []*string, hasRedirectFilter bool, -) []kongstate.Route { +) ([]kongstate.Route, error) { seenMethods := make(map[string]struct{}) routes := make([]kongstate.Route, 0) @@ -319,7 +324,9 @@ func getRoutesFromMatches( } // generate kong plugins from rule.filters - translators.ConvertFiltersToPlugins(matchRoute, filters, path, tags) + if err := translators.SetRoutePlugins(matchRoute, filters, path, tags); err != nil { + return nil, err + } routes = append(routes, *route) } else { @@ -342,7 +349,7 @@ func getRoutesFromMatches( } } } - return routes + return routes, nil } func generateKongRoutePathFromHTTPRouteMatch(match gatewayapi.HTTPRouteMatch) []string { @@ -426,9 +433,14 @@ func (p *Parser) ingressRulesFromSplitHTTPRouteMatchWithPriority( return err } + additionalRoutes, err := translators.KongExpressionRouteFromHTTPRouteMatchWithPriority(httpRouteMatchWithPriority) + if err != nil { + return err + } + kongService.Routes = append( kongService.Routes, - translators.KongExpressionRouteFromHTTPRouteMatchWithPriority(httpRouteMatchWithPriority), + *additionalRoutes, ) // cache the service to avoid duplicates in further loop iterations rules.ServiceNameToServices[serviceName] = kongService diff --git a/internal/dataplane/parser/translate_httproute_test.go b/internal/dataplane/parser/translate_httproute_test.go index 00a46665e3..ee5b6adc4d 100644 --- a/internal/dataplane/parser/translate_httproute_test.go +++ b/internal/dataplane/parser/translate_httproute_test.go @@ -1599,7 +1599,6 @@ func TestIngressRulesFromHTTPRoutesUsingExpressionRoutes(t *testing.T) { Expression: kong.String(`http.path == "/v1/foo"`), PreserveHost: kong.Bool(true), }, - Plugins: []kong.Plugin{}, ExpressionRoutes: true, }, { @@ -1608,7 +1607,6 @@ func TestIngressRulesFromHTTPRoutesUsingExpressionRoutes(t *testing.T) { Expression: kong.String(`http.path == "/v1/barr"`), PreserveHost: kong.Bool(true), }, - Plugins: []kong.Plugin{}, ExpressionRoutes: true, }, }, @@ -1703,7 +1701,6 @@ func TestIngressRulesFromHTTPRoutesUsingExpressionRoutes(t *testing.T) { Expression: kong.String(`(http.host == "foo.com") && (http.path == "/v1/foo")`), PreserveHost: kong.Bool(true), }, - Plugins: []kong.Plugin{}, ExpressionRoutes: true, }, }, @@ -1714,7 +1711,6 @@ func TestIngressRulesFromHTTPRoutesUsingExpressionRoutes(t *testing.T) { Expression: kong.String(`(http.host =^ ".bar.com") && (http.path == "/v1/foo")`), PreserveHost: kong.Bool(true), }, - Plugins: []kong.Plugin{}, ExpressionRoutes: true, }, }, @@ -1725,7 +1721,6 @@ func TestIngressRulesFromHTTPRoutesUsingExpressionRoutes(t *testing.T) { Expression: kong.String(`(http.host == "foo.com") && (http.path == "/v1/barr")`), PreserveHost: kong.Bool(true), }, - Plugins: []kong.Plugin{}, ExpressionRoutes: true, }, }, @@ -1736,7 +1731,6 @@ func TestIngressRulesFromHTTPRoutesUsingExpressionRoutes(t *testing.T) { Expression: kong.String(`(http.host =^ ".bar.com") && (http.path == "/v1/barr")`), PreserveHost: kong.Bool(true), }, - Plugins: []kong.Plugin{}, ExpressionRoutes: true, }, }, @@ -1793,7 +1787,6 @@ func TestIngressRulesFromHTTPRoutesUsingExpressionRoutes(t *testing.T) { Expression: kong.String(`(http.host == "foo.com") && (tls.sni == "foo.com") && (http.path == "/v1/foo")`), PreserveHost: kong.Bool(true), }, - Plugins: []kong.Plugin{}, ExpressionRoutes: true, }, }, @@ -1896,7 +1889,6 @@ func TestIngressRulesFromSplitHTTPRouteMatchWithPriority(t *testing.T) { StripPath: kong.Bool(false), Priority: kong.Int(1024), }, - Plugins: []kong.Plugin{}, ExpressionRoutes: true, }, }, @@ -2037,7 +2029,6 @@ func TestIngressRulesFromSplitHTTPRouteMatchWithPriority(t *testing.T) { StripPath: kong.Bool(false), Priority: kong.Int(1024), }, - Plugins: []kong.Plugin{}, ExpressionRoutes: true, }, }, @@ -2090,7 +2081,6 @@ func TestIngressRulesFromSplitHTTPRouteMatchWithPriority(t *testing.T) { StripPath: kong.Bool(false), Priority: kong.Int(1024), }, - Plugins: []kong.Plugin{}, ExpressionRoutes: true, }, }, @@ -2140,7 +2130,6 @@ func TestIngressRulesFromSplitHTTPRouteMatchWithPriority(t *testing.T) { StripPath: kong.Bool(false), Priority: kong.Int(1024), }, - Plugins: []kong.Plugin{}, ExpressionRoutes: true, }, }, diff --git a/internal/dataplane/parser/translators/httproute.go b/internal/dataplane/parser/translators/httproute.go index 9f131e2a81..618768c5d1 100644 --- a/internal/dataplane/parser/translators/httproute.go +++ b/internal/dataplane/parser/translators/httproute.go @@ -352,12 +352,21 @@ func mustMarshalJSON[T any](val T) string { return string(key) } -func ConvertFiltersToPlugins(route *kongstate.Route, filters []gatewayapi.HTTPRouteFilter, path string, tags []*string) { - plugins, pluginAnnotation := generatePluginsFromHTTPRouteFilters(filters, path, tags) - if route.Plugins == nil { - route.Plugins = make([]kong.Plugin, 0) +// SetRoutePlugins converts HTTPRouteFilter into Kong plugins. The plugins are set into the given kongstate.Route. +// The plugins can be set in two different ways: +// - Direct conversion from the respective HTTPRouteFilter. +// - ExtensionRef to plugins annotation from the ExtensionRef filter. +func SetRoutePlugins(route *kongstate.Route, filters []gatewayapi.HTTPRouteFilter, path string, tags []*string) error { + plugins, pluginAnnotation, err := generatePluginsFromHTTPRouteFilters(filters, path, tags) + if err != nil { + return err + } + if len(plugins) > 0 { + if route.Plugins == nil { + route.Plugins = make([]kong.Plugin, 0) + } + route.Plugins = append(route.Plugins, plugins...) } - route.Plugins = append(route.Plugins, plugins...) if len(pluginAnnotation) > 0 { if route.Ingress.Annotations == nil { route.Ingress.Annotations = make(map[string]string) @@ -370,17 +379,21 @@ func ConvertFiltersToPlugins(route *kongstate.Route, filters []gatewayapi.HTTPRo pluginAnnotation) } } + return nil } // generatePluginsFromHTTPRouteFilters converts HTTPRouteFilter into Kong plugins. // path is the parameter to be used by the redirect plugin, to perform redirection. -func generatePluginsFromHTTPRouteFilters(filters []gatewayapi.HTTPRouteFilter, path string, tags []*string) ([]kong.Plugin, string) { +// It returns two values: +// - A set of plugins generated by the conversion of all the provided filters, excluding ExtensionRefs. +// - A plugins annotation value, generated by the ExtensionRef filter. +func generatePluginsFromHTTPRouteFilters(filters []gatewayapi.HTTPRouteFilter, path string, tags []*string) ([]kong.Plugin, string, error) { kongPlugins := make([]kong.Plugin, 0) if len(filters) == 0 { - return kongPlugins, "" + return kongPlugins, "", nil } - var pluginsAnnotation string + var pluginsAnnotation strings.Builder for _, filter := range filters { switch filter.Type { case gatewayapi.HTTPRouteFilterRequestHeaderModifier: @@ -393,16 +406,20 @@ func generatePluginsFromHTTPRouteFilters(filters []gatewayapi.HTTPRouteFilter, p kongPlugins = append(kongPlugins, generateResponseHeaderModifierKongPlugin(filter.ResponseHeaderModifier)) case gatewayapi.HTTPRouteFilterExtensionRef: - plugin := generateExtensionRefKongPlugin(filter.ExtensionRef) - if len(pluginsAnnotation) > 0 { - pluginsAnnotation = fmt.Sprintf("%s,%s", pluginsAnnotation, plugin) + plugin, err := generateExtensionRefKongPlugin(filter.ExtensionRef) + if err != nil { + return nil, "", err + } + if len(pluginsAnnotation.String()) > 0 { + fmt.Fprintf(&pluginsAnnotation, ",%s", plugin) } else { - pluginsAnnotation = plugin + pluginsAnnotation.WriteString(plugin) } case gatewayapi.HTTPRouteFilterRequestMirror, gatewayapi.HTTPRouteFilterURLRewrite: // not supported + return nil, "", fmt.Errorf("httpFilter %s unsupported", filter.Type) } } for _, p := range kongPlugins { @@ -411,7 +428,7 @@ func generatePluginsFromHTTPRouteFilters(filters []gatewayapi.HTTPRouteFilter, p p.Tags = tags } - return kongPlugins, pluginsAnnotation + return kongPlugins, pluginsAnnotation.String(), nil } // generateRequestRedirectKongPlugin generates configurations of plugins to satisfy the specification @@ -459,8 +476,11 @@ func generateRequestRedirectKongPlugin(modifier *gatewayapi.HTTPRequestRedirectF return plugins } -func generateExtensionRefKongPlugin(modifier *gatewayapi.LocalObjectReference) string { - return string(modifier.Name) +func generateExtensionRefKongPlugin(modifier *gatewayapi.LocalObjectReference) (string, error) { + if modifier.Group != "configuration.konghq.com" || modifier.Kind != "KongPlugin" { + return "", fmt.Errorf("plugin %s/%s unsupported", modifier.Group, modifier.Kind) + } + return string(modifier.Name), nil } // generateRequestHeaderModifierKongPlugin converts a gatewayapi.HTTPRequestHeaderFilter into a diff --git a/internal/dataplane/parser/translators/httproute_atc.go b/internal/dataplane/parser/translators/httproute_atc.go index 0bef5cf02d..ef1a109e24 100644 --- a/internal/dataplane/parser/translators/httproute_atc.go +++ b/internal/dataplane/parser/translators/httproute_atc.go @@ -66,7 +66,9 @@ func GenerateKongExpressionRoutesFromHTTPRouteMatches( atc.ApplyExpression(&r.Route, routeMatcher, 1) // generate plugins. - ConvertFiltersToPlugins(&r, translation.Filters, "", tags) + if err := SetRoutePlugins(&r, translation.Filters, "", tags); err != nil { + return nil, err + } return []kongstate.Route{r}, nil } @@ -103,7 +105,9 @@ func generateKongExpressionRoutesWithRequestRedirectFilter( if match.Path != nil && match.Path.Value != nil { path = *match.Path.Value } - ConvertFiltersToPlugins(&matchRoute, translation.Filters, path, tags) + if err := SetRoutePlugins(&matchRoute, translation.Filters, path, tags); err != nil { + return nil, err + } routes = append(routes, matchRoute) } return routes, nil @@ -537,7 +541,7 @@ func compareSplitHTTPRouteMatchesRelativePriority(match1, match2 SplitHTTPRouteM // based kong route with assigned priority. func KongExpressionRouteFromHTTPRouteMatchWithPriority( httpRouteMatchWithPriority SplitHTTPRouteMatchToKongRoutePriority, -) kongstate.Route { +) (*kongstate.Route, error) { match := httpRouteMatchWithPriority.Match httproute := httpRouteMatchWithPriority.Match.Source tags := util.GenerateTagsForObject(httproute) @@ -556,7 +560,7 @@ func KongExpressionRouteFromHTTPRouteMatchWithPriority( match.MatchIndex, ) - r := kongstate.Route{ + r := &kongstate.Route{ Route: kong.Route{ Name: kong.String(routeName), PreserveHost: kong.Bool(true), @@ -590,10 +594,12 @@ func KongExpressionRouteFromHTTPRouteMatchWithPriority( path = *match.Match.Path.Value } - ConvertFiltersToPlugins(&r, rule.Filters, path, tags) + if err := SetRoutePlugins(r, rule.Filters, path, tags); err != nil { + return nil, err + } } - return r + return r, nil } // KongServiceNameFromSplitHTTPRouteMatch generates service name from split HTTPRoute match. diff --git a/internal/dataplane/parser/translators/httproute_atc_test.go b/internal/dataplane/parser/translators/httproute_atc_test.go index b097c8aee7..14c927ca2d 100644 --- a/internal/dataplane/parser/translators/httproute_atc_test.go +++ b/internal/dataplane/parser/translators/httproute_atc_test.go @@ -79,7 +79,6 @@ func TestGenerateKongExpressionRoutesFromHTTPRouteMatches(t *testing.T) { Expression: kong.String(`(http.path == "/prefix") || (http.path ^= "/prefix/")`), Priority: kong.Int(1), }, - Plugins: []kong.Plugin{}, ExpressionRoutes: true, }, }, @@ -101,7 +100,6 @@ func TestGenerateKongExpressionRoutesFromHTTPRouteMatches(t *testing.T) { Expression: kong.String(`((http.path == "/prefix") || (http.path ^= "/prefix/")) || ((http.path == "/exact") && (http.method == "GET"))`), Priority: kong.Int(1), }, - Plugins: []kong.Plugin{}, ExpressionRoutes: true, }, }, @@ -244,7 +242,6 @@ func TestGenerateKongExpressionRoutesFromHTTPRouteMatches(t *testing.T) { Expression: kong.String(`((http.path == "/prefix/0") || (http.path ^= "/prefix/0/")) && (http.host == "a.foo.com") && (tls.sni == "a.foo.com")`), Priority: kong.Int(1), }, - Plugins: []kong.Plugin{}, ExpressionRoutes: true, }, }, diff --git a/internal/dataplane/parser/translators/httproute_test.go b/internal/dataplane/parser/translators/httproute_test.go index e1115af9b7..6a6ad935b1 100644 --- a/internal/dataplane/parser/translators/httproute_test.go +++ b/internal/dataplane/parser/translators/httproute_test.go @@ -1,6 +1,7 @@ package translators import ( + "errors" "testing" "github.com/kong/go-kong/kong" @@ -17,6 +18,7 @@ func TestGeneratePluginsFromHTTPRouteFilters(t *testing.T) { path string expectedPlugins []kong.Plugin expectedPluginsAnnotation string + expectedErr error }{ { name: "no filters", @@ -24,7 +26,7 @@ func TestGeneratePluginsFromHTTPRouteFilters(t *testing.T) { expectedPlugins: []kong.Plugin{}, }, { - name: "request header modifier", + name: "request header modifier filter", filters: []gatewayapi.HTTPRouteFilter{ { Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, @@ -74,7 +76,7 @@ func TestGeneratePluginsFromHTTPRouteFilters(t *testing.T) { }, }, { - name: "request redirect modifier", + name: "request redirect modifier filter", filters: []gatewayapi.HTTPRouteFilter{ { Type: gatewayapi.HTTPRouteFilterRequestRedirect, @@ -105,7 +107,7 @@ func TestGeneratePluginsFromHTTPRouteFilters(t *testing.T) { }, }, { - name: "response header modifier", + name: "response header modifier filter", filters: []gatewayapi.HTTPRouteFilter{ { Type: gatewayapi.HTTPRouteFilterResponseHeaderModifier, @@ -155,12 +157,12 @@ func TestGeneratePluginsFromHTTPRouteFilters(t *testing.T) { }, }, { - name: "extension-refs", + name: "valid extensionrefs filters", filters: []gatewayapi.HTTPRouteFilter{ { Type: gatewayapi.HTTPRouteFilterExtensionRef, ExtensionRef: &gatewayapi.LocalObjectReference{ - Group: gatewayapi.Group("configuration.konghq.com/v1"), + Group: gatewayapi.Group("configuration.konghq.com"), Kind: gatewayapi.Kind("KongPlugin"), Name: "plugin1", }, @@ -168,7 +170,7 @@ func TestGeneratePluginsFromHTTPRouteFilters(t *testing.T) { { Type: gatewayapi.HTTPRouteFilterExtensionRef, ExtensionRef: &gatewayapi.LocalObjectReference{ - Group: gatewayapi.Group("configuration.konghq.com/v1"), + Group: gatewayapi.Group("configuration.konghq.com"), Kind: gatewayapi.Kind("KongPlugin"), Name: "plugin2", }, @@ -177,12 +179,43 @@ func TestGeneratePluginsFromHTTPRouteFilters(t *testing.T) { expectedPluginsAnnotation: "plugin1,plugin2", expectedPlugins: []kong.Plugin{}, }, + { + name: "invalid extensionrefs filter group", + filters: []gatewayapi.HTTPRouteFilter{ + { + Type: gatewayapi.HTTPRouteFilterExtensionRef, + ExtensionRef: &gatewayapi.LocalObjectReference{ + Group: gatewayapi.Group("wrong.group"), + Kind: gatewayapi.Kind("KongPlugin"), + Name: "plugin1", + }, + }, + }, + expectedPluginsAnnotation: "", + expectedErr: errors.New("plugin wrong.group/KongPlugin unsupported"), + }, + { + name: "invalid extensionrefs filter kind", + filters: []gatewayapi.HTTPRouteFilter{ + { + Type: gatewayapi.HTTPRouteFilterExtensionRef, + ExtensionRef: &gatewayapi.LocalObjectReference{ + Group: gatewayapi.Group("configuration.konghq.com"), + Kind: gatewayapi.Kind("WrongKind"), + Name: "plugin1", + }, + }, + }, + expectedPluginsAnnotation: "", + expectedErr: errors.New("plugin configuration.konghq.com/WrongKind unsupported"), + }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { - plugins, pluginsAnnotation := generatePluginsFromHTTPRouteFilters(tc.filters, tc.path, nil) + plugins, pluginsAnnotation, err := generatePluginsFromHTTPRouteFilters(tc.filters, tc.path, nil) + require.Equal(t, tc.expectedErr, err) require.Equal(t, tc.expectedPlugins, plugins) require.Equal(t, tc.expectedPluginsAnnotation, pluginsAnnotation) })