HTTPRoute RequestRedirect doesn't work with ReplacePrefixMatch
What is the issue?
If you try to use an HTTPRoute RequestRedirect filter with the ReplacePrefixMatch operation, the HTTPRoute will be accepted, but any route defined by that HTTPRoute will fail with an HTTP 500 and l5d-proxy-error: unexpected error. The culprit ultimately seems to be this bit in linkerd/http/route/src/http/filter/redirect.rs in the proxy:
api::path_modifier::Replace::Prefix(prefix) => {
if prefix.starts_with('/') {
return Err(InvalidRequestRedirect::RelativePath);
}
Ok(ModifyPath::ReplacePrefixMatch(prefix))
}
This seems to be saying that the ReplacePrefixMatch option requires a relative path. However:
-
linkerd-policy-validatorwill not allow a relative path there; it requires any replacement path to be absolute. - To further confuse things, if you use an absolute path, the error is reported as "paths must be absolute"... which it already is.
The right answer would seem to be to allow either a relative path or an absolute path for ReplacePrefixMatch: they both make sense. At minimum, though, if the proxy will only accept a relative path, then the validator must allow a relative path!
How can it be reproduced?
Get an empty cluster (I was using k3d), then:
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml
linkerd install --crds | kubectl apply -f -
linkerd install | kubectl apply -f -
linkerd check
kubectl create ns faces
kubectl annotate ns/faces linkerd.io/inject=enabled
helm install \
faces -n faces \
oci://ghcr.io/buoyantio/faces-chart --version 2.0.0 \
--set gui.serviceType=LoadBalancer \
--set smiley2.enabled=true \
--wait
kubectl apply -f - <<EOF
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tools
namespace: faces
spec:
replicas: 1
selector:
matchLabels:
app: tools
template:
metadata:
labels:
app: tools
spec:
containers:
- name: tools
args:
- -c
- |
sleep 86400
command:
- /bin/sh
image: jonlabelle/network-tools
EOF
kubectl rollout status -n faces deploy
At this point we have a running Faces installation, fully meshed, and
kubectl exec -it -n faces deploy/tools -c tools -- curl -v http://smiley/
will work (it'll return a 200 and a JSON body).
Next up, apply an HTTPRoute with an unconditional redirect using full path replacement:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: mesh-redirect-path
namespace: faces
spec:
parentRefs:
- group: ""
kind: Service
name: smiley
port: 80
rules:
- filters:
- type: RequestRedirect
requestRedirect:
path:
type: ReplaceFullPath
replaceFullPath: /replace-full-path
statusCode: 301
EOF
Rerun the curl:
kubectl exec -it -n faces deploy/tools -c tools -- curl -v http://smiley/
and you'll get a 301 with location: http://smiley/replace-full-path. So far so good.
Now update the Route to use ReplacePrefixMatch:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: mesh-redirect-path
namespace: faces
spec:
parentRefs:
- group: ""
kind: Service
name: smiley
port: 80
rules:
- filters:
- type: RequestRedirect
requestRedirect:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /replace-prefix-match
statusCode: 301
EOF
Rerun the curl:
kubectl exec -it -n faces deploy/tools -c tools -- curl -v http://smiley/
and you'll get a 500 with l5d-proxy-error: unexpected error... but the proxy logs for the outgoing proxy have more. Buried in the output of kubectl logs -n faces deploy/tools -c linkerd-proxy you'll find this bit:
[ 424.629421s] WARN ThreadId(01) outbound:proxy{addr=10.43.239.204:80}: linkerd_app_outbound::policy::api: Client policy misconfigured error=invalid HTTP route: invalid filter: invalid HTTP redirect: redirect paths must be absolute
Finally, one last point. We can throw other rules in there, too: this Route should redirect only when the incoming path starts with /redirect, and allow other paths through unmodified.
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: mesh-redirect-path
namespace: faces
spec:
parentRefs:
- group: ""
kind: Service
name: smiley
port: 80
rules:
- matches:
- path:
type: PathPrefix
value: /redirect
filters:
- type: RequestRedirect
requestRedirect:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /replace-prefix-match
statusCode: 301
- backendRefs:
- group: ""
kind: Service
name: smiley
port: 80
EOF
but with that in place, everything still fails because of the one broken rule, so both of these will 500:
kubectl exec -it -n faces deploy/tools -c tools -- curl -v http://smiley/
kubectl exec -it -n faces deploy/tools -c tools -- curl -v http://smiley/redirect/
Logs, error output, etc
See above.
output of linkerd check -o short
~/e/gateway-api [☸ k3d-conf] on flynn/linkerd-conformance [$»!?]
:; linkerd check -o short
Status check results are √
Environment
K3d running v1.30.6+k3s1 on MacOS with OrbStack, Linkerd enterprise-2.18 and edge-25.6.1.
Possible solution
No response
Additional context
No response
Would you like to work on fixing this bug?
None
I'm not sure, but it seems like in this case the path must be absolute and there's simply a bug validating that. i.e.
if prefix.starts_with('/') {
return Err(InvalidRequestRedirect::RelativePath);
}
is supposed to be
if !prefix.starts_with('/') {
return Err(InvalidRequestRedirect::RelativePath);
}
This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. Thank you for your contributions.