istio: Can't rewrite to empty string

Please see here for a workaround.

Describe the bug

I have a VirtualService connected to a Gateway to allow traffic into my cluster.

I want to remove /service-a from the URL, so that http://site/service-a hits / on service-a, http://site/service-a/metrics hits /metrics, etc.

Works, but not completely

This VirtualService works for all cases except, of course, http://site/service-a (without trailing slash)

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: service-a
spec:
  hosts:
  - "hostname.example.org"
  gateways:
  - hostname-gateway
  http:
  - match:
    - uri:
        prefix: "/service-a/"
    rewrite:
      uri: "/"
    route:
    - destination:
        port:
          number: 8080
        host: service-a

Doesn’t work

http:
  - match:
    - uri:
        prefix: "/service-a"
    rewrite:
      uri: ""

Passes to the envoy on the client and then returns a 404 for /service-a (a handler the service doesn’t have)

curl -D- http://hostname.example.org/service-a
HTTP/1.1 404 Not Found
x-powered-by: Express
content-security-policy: default-src 'self'
x-content-type-options: nosniff
content-type: text/html; charset=utf-8
content-length: 148
date: Mon, 20 Aug 2018 15:50:21 GMT
x-envoy-upstream-service-time: 1
server: envoy

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /service-a</pre>
</body>
</html>

Tried "", ~ or '' as the empty string. The empty string seems to be persisted:

$ kubectl get virtualservice/service-a -ojson
        "http": [
            {
                "match": [
                    {
                        "uri": {
                            "prefix": "/service-a"
                        }
                    }
                ],
                "rewrite": {
                    "uri": ""
                },

Matching the prefix works if the replacement string is something

  - match:
    - uri:
        prefix: "/service-a"
    rewrite:
      uri: "/"

Results in error Cannot GET //

  - match:
    - uri:
        prefix: "/service-a"
    rewrite:
      uri: " "

Results in 400

  http:
  - match:
    - uri:
        prefix: "/service-a/"
    rewrite:
      uri: ""

Version

GitRevision: 6f9f420f0c7119ff4fa6a1966a6f6d89b1b4db84
User: root@48d5ddfd72da
Hub: docker.io/istio
GolangVersion: go1.10.1
BuildStatus: Clean

Client Version: version.Info{Major:"1", Minor:"11", GitVersion:"v1.11.0", GitCommit:"91e7b4fd31fcd3d5f436da26c980becec37ceefe", GitTreeState:"clean", BuildDate:"2018-06-27T22:29:25Z", GoVersion:"go1.10.3", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"10+", GitVersion:"v1.10.5-gke.4", GitCommit:"6265b9797fc8680c8395abeab12c1e3bad14069a", GitTreeState:"clean", BuildDate:"2018-08-04T03:47:40Z", GoVersion:"go1.9.3b4", Compiler:"gc", Platform:"linux/amd64"}```

(No, I haven’t tested 1.0)

Is Istio Auth enabled or not? No

Environment GKE

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 24
  • Comments: 29 (10 by maintainers)

Most upvoted comments

Adding another match uri accomplishes what you’re looking for, though a bit more verbose

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: echoserver
spec:
  hosts:
  - "example.com"
  gateways:
  - example-gateway
  http:
  - match:
    - uri:
        prefix: "/echo/"
    - uri:
        prefix: "/echo"
    rewrite:
      uri: "/" 
    route:
    - destination:
        host: echoserver
        port:
          number: 8080

I was having the same problem (double forward slash).

Got it working with this:

  http:
  - match:
    - uri:
        prefix: /example
    rewrite:
      uri: " "
    route:
    - destination:
        host: example-backend.default.svc.cluster.local
        port:
          number: 3001

I’ve just replaced the uri parameter with a whitespace.

I tought it would throw me a configuration error but I guess it just strips of the whitespace when rewriting (I don’t know if this behavior is intended though).

Looks like this can solve your problem

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: service-a
spec:
  hosts:
  - "hostname.example.org"
  gateways:
  - hostname-gateway
  http:
  - match:
    - uri:
        prefix: "/service-a/"
    rewrite:
      uri: "/"
    route:
    - destination:
        port:
          number: 8080
        host: service-a

hostname.example.org/service-a/xxxxxxx ======> hostname.example.org/xxxxxxx

Hope to help you!

Got it working

  - match:
    - uri:
        prefix: /example/
    - uri:
        exact: /example
    rewrite:
      uri: /
    route:
    - destination:
        host: example-svc
        port:
          number: 80

It feels like a bug that there’s no way to the empty string, so I would like it to stay open to track please.

Could we please just make it work the way we expect?

@mleneveut you aren’t doing it the way @buckhx described it

The matches are ordered. The first one to match is going to be executed. Thus you need to match the more specific prefix first.

Let’s say you call /api/inspection-validation, having the following match defined

- match:
    - uri:
        prefix: "/api"
    - uri:
        prefix: "/api/"
    rewrite:
      uri: /

It’s going to rewrite the path to //inspection-validation, the emphasis lies on the leading //.

That’s what’s causing the weird behaviour you encountered. Just change the order 😉

https://github.com/istio/istio/issues/8076#issuecomment-572394633

The above fix by @buckhx works nicely and I was using it for quite a while… Until I’ve found a weakness in it ! Say, you create another VirtualService (called ‘echotwo’) behind the same gateway. Now if you request: example.com/echotwo the gateway will route it to ‘echo’ not ‘echotwo’ VirtualService, because: prefix: "/echo" matches everything: /echo /echo/whattever /echotwo /echotwo/whatever

The fix is simple. Just replace: prefix: "/echo" with: exact: "/echo" it will prevent overzealous match, and both VirtualServices will work correctly.

So, the fixed example is:

kind: VirtualService
metadata:
  name: echoserver
spec:
  hosts:
  - "example.com"
  gateways:
  - example-gateway
  http:
  - match:
    - uri:
        prefix: "/echo/"
    - uri:
        exact: "/echo"
    rewrite:
      uri: "/" 
    route:
    - destination:
        host: echoserver
        port:
          number: 8080

I have something like it

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: greeting-virtual-service
spec:
  hosts:
    - "*"
  gateways:
    - gateway
  http:
    - match:
      - uri:
          exact: "/api"
      - uri:
          prefix: "/api/"
      rewrite:
        uri: "/"
      route:
        - destination:
            host: java-greeting-service

the “/api” and “/api/” go to java-greeting-service “/”

This is a problem for us too. Empty space in a re-write goes to the API and route cannot be resolved. /token and /token/ rewrite do not work either.

We end up having two virtual services for every API:

  • Internal virtual service
  • External virtual service

External service should match on http://host/api-name/action and rewrite to http://api-name.cluster.svc.local/action. Since I can’t remove the /api-name/ prefix, I’m forced to have duplicate entries in internal and external virtual services. 30 services running within the mesh, each exposes 5-10 end-points and we have a lot of extra work on our hands. It would make a lot of sense to support replacement of URL prefixes.

Rewriting to " " seemed like a good idea, but doesn’t look like .net core trims empty spaces, so we end up with failed requests to the API. Given a number of discussions, it would be good to see this moved out of nebulous future milestone.

I have the same problem, and the solution of @buckhx does not work for me.

Istio 1.0.4 Kubernetes 1.11.3 in Azure (AKS)

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: iv
  namespace: istio-system
spec:
  gateways:
  - istio-https-k8s-ingress
  hosts:
  - xxx.westeurope.cloudapp.azure.com
  http:
  - match:
    - uri:
        prefix: /domain-api
    route:
    - destination:
        host: da.poc.svc.cluster.local
        port:
          number: 80
  - match:
    - uri:
        prefix: /api
    rewrite:
      uri: /
    route:
    - destination:
        host: bff.poc.svc.cluster.local
        port:
          number: 80
  - match:
    - uri:
        prefix: /api/
    rewrite:
      uri: /
    route:
    - destination:
        host: bff.poc.svc.cluster.local
        port:
          number: 80
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: spa.poc.svc.cluster.local
        port:
          number: 80

(If I put the 2 prefix in the same match, the website does not respond. No specific logs in ingress or pilot)

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: istio-https-k8s-ingress
  namespace: istio-system
spec:
  selector:
    istio: ingressgateway
  servers:
  - hosts:
    - '*'
    port:
      name: http
      number: 80
      protocol: HTTP
  - hosts:
    - '*'
    port:
      name: https
      number: 443
      protocol: HTTPS
    tls:
      mode: SIMPLE
      privateKey: /etc/istio/ingressgateway-certs/tls.key
      serverCertificate: /etc/istio/ingressgateway-certs/tls.crt

The SPA is working, but any call to https://xxx.westeurope.cloudapp.azure.com/api/yyy get a 404. I can see in the ingress logs that it wants to redirect to the BFF, but does not seem to rewrite to / so that the BFF is called with /yyy instead of /api/yyy :

[2018-12-03T09:59:58.142Z] "GET /api/inspection-validation/inspections/HTTP/2" 404 - 0 174 0 0 "10.xxx.1.1" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" "2dfc455a-ac72-4e6d-9014-1406958a08a2" "xxx.westeurope.cloudapp.azure.com" "10.xxx.1.116:80" outbound|80||bff.poc.svc.cluster.local - 10.xxx.1.120:443 10.xxx.1.1:4977

Any idea ?

Edit :

  - match:
    - uri:
        prefix: /api/inspection-validation
    rewrite:
      uri: /inspection-validation
    route:
    - destination:
        host: bff.poc.svc.cluster.local
        port:
          number: 80

is working, but it’s too specific, I’d like to redirect /api/* to /*

@ClareChu solution works perfectly

Unfortunately, I don’t believe using prefix: /example and a single space works unless your request ends in a /. That works great for requests to /example/ but not /example. Setting the rewrite to / does the opposite… it works for /example but if you have anything after it (starting with another /) you end up with the // problem.

The only way I see that actually solves this is the two-rule setup that @buckhx posted.

By having a rule that matches /path/ followed by another rule for /path, with both getting replaced with / prevents the // problem by making sure both get captured when they’re present.

@craigbox I think we can close the issue if resolved.