Ingress-nginx: Ingress path with regular expression requires rewrite-target annotation

Created on 18 Apr 2017  路  4Comments  路  Source: kubernetes/ingress-nginx

https://github.com/kubernetes/kubernetes/blob/master/pkg/apis/extensions/v1beta1/types.go#L690:

Path is an extended POSIX regex as defined by IEEE Std 1003.1, (i.e this follows the egrep/unix syntax, not the perl syntax) matched against the path of an incoming request. Currently it can contain characters disallowed from the conventional "path" part of a URL as defined by RFC 3986. Paths must begin with a '/'.

kind: Ingress
apiVersion: extensions/v1beta1
metadata:
  name: re-path-ingress
spec:
  rules:
  - host: "echo.example.com"
    http:
      paths:
      - path: "/.*/foo"
        backend:
          serviceName: echoserver
          servicePort: http

This by itself does not work, and instead creates an ingress for literally '/.*/foo'. Adding a rewrite-target however makes things magically behave differently:

kind: Ingress
apiVersion: extensions/v1beta1
metadata:
  name: re-path-ingress
  annotations:
    ingress.kubernetes.io/rewrite-target: "/"
spec:
  rules:
  - host: "echo.example.com"
    http:
      paths:
      - path: "/.*/foo"
        backend:
          serviceName: echoserver
          servicePort: http

This is in my eyes very surprising, and I could not find any documentation describing this behavior. If this is an intentional limitation of the nginx ingress controller (tested with 0.9.0-beta.3), then it should be minimally documented somewhere.

FWIW: Source is buildLocation: only when there is a rewrite-target will it produce a regex location.


This is a follow-up/fold-out from #565.

lifecyclrotten

Most helpful comment

Can this be reopened? Without something like an annotation to set what type of match the location should use that is not just a prefix match, the rewrite-target annotation needs to be used. However, using the rewrite-target has some other specific properties of its own. For instance, if I wanted locations like the following:

location ~* ^/a/app2/api {
    ...
}

location ~* ^/a/app1/api {
   ...
}

location ~* ^/a($|/) {
   ...
}

location / {
  ...
}

so that paths like the following would all go to different services: /a/app/api/blah, /a/app2/api/blah, /a/settings, and /apps, some workarounds need to be done.

The first problem is that you cannot set a rewrite-target and a path that match and still get a regex match because when the location is built, this is checked:
https://github.com/kubernetes/ingress-nginx/blob/d744c2eba78e084801907bb79af694defa7c487d/internal/ingress/controller/template/template.go#L201

So just doing the following will not work:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    ingress.kubernetes.io/rewrite-target: /a/app/api
  name: rewrite
  namespace: default
spec:
  rules:
  - host: rewrite.bar.com
    http:
      paths:
      - backend:
          serviceName: echoheaders
          servicePort: 80
        path: /a/app/api

They need to be different, which you can get around by changing the path to:

        path: /a/app/api/?

This results in a location like

location ~* ^/a/app/api/?\/?(?<baseuri>.*)

and a rewrite rule like

rewrite /a/app/api/?/(.*) /a/app/api/$1 break;

which works for the longer matches, but is a useless rewrite rule.

For matching on the /a, we would want a path like

        path: /a($|/)

so that we do not match on things like /apps. But since there is no way to just specify a regex match for a location, a rewrite-target needs defined (in this case just /a) which results in the extra regex added on to the location match, as well as the following useless rewrite rule

rewrite /a($|/)/(.*) /a/$1 break;

because it does not even match against the path (the double /s).

Having just an annotation like nginx.ingress.kubernetes.io/location-regex-match: true or something would be much more clear (and better optimized since there is no rewrite rule created) instead of defining the same path as the rule. Even just allowing the rewrite-target and the path to match would be better, and I cannot think of a reason why it may be bad to not allow it.

All 4 comments

Issues go stale after 90d of inactivity.
Mark the issue as fresh with /remove-lifecycle stale.
Stale issues rot after an additional 30d of inactivity and eventually close.

Prevent issues from auto-closing with an /lifecycle frozen comment.

If this issue is safe to close now please do so with /close.

Send feedback to sig-testing, kubernetes/test-infra and/or @fejta.
/lifecycle stale

Stale issues rot after 30d of inactivity.
Mark the issue as fresh with /remove-lifecycle rotten.
Rotten issues close after an additional 30d of inactivity.

If this issue is safe to close now please do so with /close.

Send feedback to sig-testing, kubernetes/test-infra and/or @fejta.
/lifecycle rotten
/remove-lifecycle stale

Rotten issues close after 30d of inactivity.
Reopen the issue with /reopen.
Mark the issue as fresh with /remove-lifecycle rotten.

Send feedback to sig-testing, kubernetes/test-infra and/or fejta.
/close

Can this be reopened? Without something like an annotation to set what type of match the location should use that is not just a prefix match, the rewrite-target annotation needs to be used. However, using the rewrite-target has some other specific properties of its own. For instance, if I wanted locations like the following:

location ~* ^/a/app2/api {
    ...
}

location ~* ^/a/app1/api {
   ...
}

location ~* ^/a($|/) {
   ...
}

location / {
  ...
}

so that paths like the following would all go to different services: /a/app/api/blah, /a/app2/api/blah, /a/settings, and /apps, some workarounds need to be done.

The first problem is that you cannot set a rewrite-target and a path that match and still get a regex match because when the location is built, this is checked:
https://github.com/kubernetes/ingress-nginx/blob/d744c2eba78e084801907bb79af694defa7c487d/internal/ingress/controller/template/template.go#L201

So just doing the following will not work:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    ingress.kubernetes.io/rewrite-target: /a/app/api
  name: rewrite
  namespace: default
spec:
  rules:
  - host: rewrite.bar.com
    http:
      paths:
      - backend:
          serviceName: echoheaders
          servicePort: 80
        path: /a/app/api

They need to be different, which you can get around by changing the path to:

        path: /a/app/api/?

This results in a location like

location ~* ^/a/app/api/?\/?(?<baseuri>.*)

and a rewrite rule like

rewrite /a/app/api/?/(.*) /a/app/api/$1 break;

which works for the longer matches, but is a useless rewrite rule.

For matching on the /a, we would want a path like

        path: /a($|/)

so that we do not match on things like /apps. But since there is no way to just specify a regex match for a location, a rewrite-target needs defined (in this case just /a) which results in the extra regex added on to the location match, as well as the following useless rewrite rule

rewrite /a($|/)/(.*) /a/$1 break;

because it does not even match against the path (the double /s).

Having just an annotation like nginx.ingress.kubernetes.io/location-regex-match: true or something would be much more clear (and better optimized since there is no rewrite rule created) instead of defining the same path as the rule. Even just allowing the rewrite-target and the path to match would be better, and I cannot think of a reason why it may be bad to not allow it.

Was this page helpful?
0 / 5 - 0 ratings