Skip to content

Commit e4020e8

Browse files
authored
feat(hybridgateway): add support to PathPrefixMatch RequestRedirect (#3065)
Signed-off-by: Francesco Giudici <francesco.giudici@konghq.com>
1 parent 98c668e commit e4020e8

File tree

3 files changed

+544
-27
lines changed

3 files changed

+544
-27
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
For this reference to be allowed, a `KongReferenceGrant` resource must be created
5454
in the namespace of the `KongPlugin`, allowing access for the `KongPluginBinding`.
5555
[#31038](https://github.com/Kong/kong-operator/pull/3108)
56+
- HybridGateway: Added support to PathPrefixMatch for the `RequestRedirect` `HTTPRoute` filter.
57+
[#3065](https://github.com/Kong/kong-operator/pull/3065)
5658

5759
### Fixes
5860

controller/hybridgateway/plugin/converter.go

Lines changed: 108 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,27 @@ func translateFromFilter(rule gwtypes.HTTPRouteRule, filter gwtypes.HTTPRouteFil
6666
pData.config = configJSON
6767
pluginConfs = append(pluginConfs, pData)
6868
case gatewayv1.HTTPRouteFilterRequestRedirect:
69-
pData := kongPluginConfig{name: "redirect"}
69+
var pluginName string
70+
var config any
71+
var err error
72+
rr := filter.RequestRedirect
73+
74+
// Decide which plugin to use based on the path modifier type.
75+
// If it's a PrefixMatch, we need to use a custom Lua pre-function to handle the redirect properly
76+
// otherwise we can use the standard redirect plugin.
77+
if rr.Path != nil && rr.Path.Type == gatewayv1.PrefixMatchHTTPPathModifier {
78+
config, err = translateRequestRedirectPreFunction(filter, rule)
79+
pluginName = "pre-function"
80+
} else {
81+
config, err = translateRequestRedirect(filter)
82+
pluginName = "redirect"
83+
}
7084

71-
config, err := translateRequestRedirect(filter)
85+
// From here on
7286
if err != nil {
7387
return nil, fmt.Errorf("translating RequestRedirect filter: %w", err)
7488
}
89+
pData := kongPluginConfig{name: pluginName}
7590
configJSON, err := json.Marshal(config)
7691
if err != nil {
7792
return nil, fmt.Errorf("failed to marshal %q plugin config: %w", pData.name, err)
@@ -199,6 +214,85 @@ func translateResponseModifier(filter gwtypes.HTTPRouteFilter) (transformerData,
199214
return plugin, err
200215
}
201216

217+
type accessPreFunctionConfig struct {
218+
Access []string `json:"access"`
219+
}
220+
221+
func translateRequestRedirectPreFunction(filter gwtypes.HTTPRouteFilter, rule gwtypes.HTTPRouteRule) (accessPreFunctionConfig, error) {
222+
rr := filter.RequestRedirect
223+
224+
if rr == nil {
225+
return accessPreFunctionConfig{}, errors.New("RequestRedirect filter config is missing")
226+
}
227+
228+
// Kong does not have a direct equivalent for prefix match replacement in redirects.
229+
// We need to deal it with a custom pre-function so that we can access the captured groups.
230+
sourcePathPrefix := getPathPrefixMatchValue(rule)
231+
targetPathPrefix := lo.FromPtrOr(rr.Path.ReplacePrefixMatch, "")
232+
targetHost := string(lo.FromPtrOr(rr.Hostname, ""))
233+
customScheme := ""
234+
if rr.Scheme != nil && *rr.Scheme != "" {
235+
customScheme = *rr.Scheme
236+
}
237+
customCode := 302
238+
if rr.StatusCode != nil {
239+
customCode = *rr.StatusCode
240+
}
241+
242+
funcBody := translateRequestRedirectGenerateFunctionBody(
243+
sourcePathPrefix,
244+
targetPathPrefix,
245+
targetHost,
246+
customScheme,
247+
customCode)
248+
return accessPreFunctionConfig{Access: []string{funcBody}}, nil
249+
}
250+
251+
func translateRequestRedirectGenerateFunctionBody(
252+
sourcePathPrefix, targetPathPrefix, targetHost, customScheme string,
253+
customCode int) string {
254+
return fmt.Sprintf(`
255+
-- Inputs
256+
local match_prefix = [[%s]]
257+
local custom_prefix = [[%s]]
258+
local custom_host = [[%s]]
259+
local custom_scheme = [[%s]]
260+
local code = %d
261+
262+
-- Scheme: use custom_scheme if provided, else preserve original
263+
local scheme = custom_scheme
264+
if not scheme or scheme == "" then
265+
scheme = kong.request.get_scheme() or "http"
266+
end
267+
268+
-- Host: use custom_host if provided, else preserve original
269+
local host = custom_host
270+
271+
-- Prefer forwarded host if present (more accurate in proxied setups)
272+
if not host or host == "" then
273+
host = kong.request.get_forwarded_host() or kong.request.get_host()
274+
end
275+
276+
-- Get request path and raw query string
277+
local path = kong.request.get_path() or "/"
278+
local qs = kong.request.get_raw_query()
279+
if path:sub(1, #match_prefix) == match_prefix then
280+
local remainder = path:sub(#match_prefix + 1)
281+
if remainder == "" then
282+
remainder = "/"
283+
end
284+
-- Build redirect target
285+
local new_path = custom_prefix .. remainder
286+
local location = scheme .. "://" .. host .. new_path
287+
if qs and qs ~= "" then
288+
location = location .. "?" .. qs
289+
end
290+
-- Issue redirect
291+
return kong.response.exit(code, "", { ["Location"] = location })
292+
end
293+
`, sourcePathPrefix, targetPathPrefix, targetHost, customScheme, customCode)
294+
}
295+
202296
type requestRedirectConfig struct {
203297
KeepIncomingPath bool `json:"keep_incoming_path"`
204298
Location string `json:"location"`
@@ -250,39 +344,34 @@ func translateRequestRedirectHostname(rr *gatewayv1.HTTPRequestRedirectFilter) s
250344
}
251345

252346
func translateRequestRedirectPath(rr *gatewayv1.HTTPRequestRedirectFilter) (string, error) {
253-
path := ""
347+
pluginPath := ""
254348
var err error
255349

256350
if rr.Path == nil {
257-
return path, nil
351+
return pluginPath, nil
258352
}
259353

260354
pathModifier := rr.Path
261355
switch pathModifier.Type {
262356
case gatewayv1.FullPathHTTPPathModifier:
263-
path = translateRequestRedirectPathFullPath(pathModifier.ReplaceFullPath)
357+
pluginPath = translatePathReplaceFullPath(pathModifier.ReplaceFullPath)
358+
264359
case gatewayv1.PrefixMatchHTTPPathModifier:
265-
path = translateRequestRedirectPathPrefixMatch(pathModifier.ReplacePrefixMatch)
360+
// Nothing to do here, handled in translateFromFilter to allow proper ordering of plugins.
361+
pluginPath = ""
266362
default:
267363
err = errors.New("unsupported RequestRedirect path modifier type: " + string(pathModifier.Type))
268364
}
269-
return path, err
365+
return pluginPath, err
270366
}
271367

272-
func translateRequestRedirectPathFullPath(replaceFullPath *string) string {
368+
func translatePathReplaceFullPath(replaceFullPath *string) string {
273369
if replaceFullPath == nil || *replaceFullPath == "" {
274370
return "/"
275371
}
276372
return *replaceFullPath
277373
}
278374

279-
func translateRequestRedirectPathPrefixMatch(prefixMatch *string) string {
280-
// Not implemented yet - Kong does not have a direct equivalent for prefix match replacement.
281-
// KIC in Konnect just ignores PrefixMatch filters, let's do the same.
282-
// Tracker: https://github.com/Kong/kong-operator/issues/2466
283-
return "/"
284-
}
285-
286375
func translateURLRewrite(filter gwtypes.HTTPRouteFilter, path string) (transformerData, error) {
287376
ur := filter.URLRewrite
288377
pluginConf := transformerData{}
@@ -300,9 +389,9 @@ func translateURLRewrite(filter gwtypes.HTTPRouteFilter, path string) (transform
300389
if ur.Path != nil {
301390
switch ur.Path.Type {
302391
case gatewayv1.FullPathHTTPPathModifier:
303-
pluginConf.Replace.Uri = translateURLRewritePathFullPath(ur.Path.ReplaceFullPath)
392+
pluginConf.Replace.Uri = translatePathReplaceFullPath(ur.Path.ReplaceFullPath)
304393
case gatewayv1.PrefixMatchHTTPPathModifier:
305-
pluginConf.Replace.Uri = translateURLRewritePathPrefixMatch(
394+
pluginConf.Replace.Uri = translatePathReplacePrefixMatch(
306395
normalizePath(ur.Path.ReplacePrefixMatch),
307396
normalizePath(&path))
308397
default:
@@ -320,18 +409,11 @@ func normalizePath(path *string) string {
320409
return strings.TrimSuffix(*path, "/")
321410
}
322411

323-
func translateURLRewritePathFullPath(replaceFullPath *string) string {
324-
if replaceFullPath == nil || *replaceFullPath == "" {
325-
return "/"
326-
}
327-
return *replaceFullPath
328-
}
329-
330-
// translateURLRewritePathPrefixMatch generates the replacement URI for the request-transformer
412+
// translatePathReplacePrefixMatch generates the replacement URI for the request-transformer
331413
// plugin for the URLRewrite filter with a PrefixMatchHTTPPathModifier.
332414
// The logic here is copied from KIC's implementation to ensure consistent behavior, see:
333415
// https://github.com/Kong/kubernetes-ingress-controller/blob/main/internal/dataplane/translator/subtranslator/httproute.go#L1434.
334-
func translateURLRewritePathPrefixMatch(replacePrefixMatch string, path string) string {
416+
func translatePathReplacePrefixMatch(replacePrefixMatch string, path string) string {
335417
// Trim the trailing slash from the ReplacePrefixMatch to avoid double slashes in the final URI.
336418
replacePrefixMatch = strings.TrimSuffix(replacePrefixMatch, "/")
337419
pathIsRoot := path == "/"

0 commit comments

Comments
 (0)