Skip to content

HTTPRoute RequestRedirect doesn't work with ReplacePrefixMatch #14110

@kflynn

Description

@kflynn

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:

  1. linkerd-policy-validator will not allow a relative path there; it requires any replacement path to be absolute.
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions