ingress-nginx icon indicating copy to clipboard operation
ingress-nginx copied to clipboard

Redirect annotations are ignored on default backend.

Open kevincox opened this issue 2 years ago • 19 comments

What happened:

Create an ingress for the default backend like so:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/permanent-redirect: https://example.com
  name: default-backend
  namespace: nginx-ingress
spec:
  defaultBackend:
    service:
      name: default-backend
      port:
        name: http

What you expected to happen:

Requests hitting the default backend were redirected.

Instead this annotation was ignored. It is may be related to https://github.com/kubernetes/ingress-nginx/issues/1405. This also doesn't work for nginx.ingress.kubernetes.io/temporal-redirect.

A workaround is to use a snippet which works as intended:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/configuration-snippet: |
      return 301 https://example.com;
spec:
  defaultBackend:
    service:
      name: default-backend
      port:
        name: http

NGINX Ingress controller version

NGINX Ingress controller
  Release:       v1.2.1
  Build:         08848d69e0c83992c89da18e70ea708752f21d7a
  Repository:    https://github.com/kubernetes/ingress-nginx
  nginx version: nginx/1.19.10

Kubernetes version (use kubectl version):

Client Version: version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.8", GitCommit:"a12b886b1da059e0190c54d09c5eab5219dd7acf", GitTreeState:"archive", BuildDate:"1980-01-01T00:00:00Z", GoVersion:"go1.17.10", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.8", GitCommit:"7061dbbf75f9f82e8ab21f9be7e8ffcaae8e0d44", GitTreeState:"clean", BuildDate:"2022-03-16T14:04:34Z", GoVersion:"go1.16.15", Compiler:"gc", Platform:"linux/amd64"}

Environment:

  • How was the ingress-nginx-controller installed:
Kubernetes Manifests
apiVersion: v1
items:
- apiVersion: v1
  kind: Service
  metadata:
    annotations:
    name: default-backend
    namespace: nginx-ingress
  spec:
    ports:
    - name: http
      port: 8181
      targetPort: 8181
    selector:
      app: nginx
    type: ClusterIP
- apiVersion: v1
  kind: Service
  metadata:
    labels:
      external-dns: "true"
    name: nginx
    namespace: nginx-ingress
  spec:
    ports:
    - name: http
      port: 8080
      targetPort: 8080
    - name: https
      port: 8443
      targetPort: 8443
    selector:
      app: nginx
    type: NodePort
- apiVersion: apps/v1
  kind: DaemonSet
  metadata:
    name: nginx
    namespace: nginx-ingress
  spec:
    selector:
      matchLabels:
        app: nginx
    template:
      metadata:
        labels:
          app: nginx
      spec:
        containers:
        - args:
          - /nginx-ingress-controller
          - --configmap=nginx-ingress/nginx-configuration
          - --default-backend-service=nginx-ingress/default-backend
          - --default-server-port=8181
          - --election-id=ingress-controller-leader
          - --http-port=8080
          - --https-port=8443
          - --watch-ingress-without-class=true
          env:
          - name: LD_PRELOAD
            value: /usr/local/lib/libmimalloc.so
          - name: POD_NAME
            valueFrom:
              fieldRef:
                apiVersion: v1
                fieldPath: metadata.name
          - name: POD_NAMESPACE
            valueFrom:
              fieldRef:
                apiVersion: v1
                fieldPath: metadata.namespace
          image: k8s.gcr.io/ingress-nginx/controller:v1.2.1@sha256:5516d103a9c2ecc4f026efbd4b40662ce22dc1f824fb129ed121460aaa5c47f8
          imagePullPolicy: IfNotPresent
          lifecycle:
            preStop:
              exec:
                command:
                - /wait-shutdown
          livenessProbe:
            failureThreshold: 2
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            periodSeconds: 2
            successThreshold: 1
            timeoutSeconds: 1
          name: nginx
          ports:
          - containerPort: 8080
            hostPort: 80
          - containerPort: 8443
            hostPort: 443
          resources:
            requests:
              cpu: 100m
              memory: 100Mi
          securityContext:
            allowPrivilegeEscalation: true
            capabilities:
              add:
              - NET_BIND_SERVICE
              drop:
              - ALL
            privileged: false
            readOnlyRootFilesystem: false
            runAsNonRoot: true
            runAsUser: 101
          startupProbe:
            failureThreshold: 60
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            periodSeconds: 2
            successThreshold: 1
            timeoutSeconds: 1
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
        restartPolicy: Always
        securityContext: {}
        terminationGracePeriodSeconds: 30
    updateStrategy:
      rollingUpdate:
        maxSurge: 0
        maxUnavailable: 1
      type: RollingUpdate
- apiVersion: networking.k8s.io/v1
  kind: Ingress
  metadata:
    annotations:
      nginx.ingress.kubernetes.io/configuration-snippet: |
        return 301 https://example.com;
    name: default-backend
    namespace: nginx-ingress
  spec:
    defaultBackend:
      service:
        name: default-backend
        port:
          name: http
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""

How to reproduce this issue:

Install minikube/kind

  • Minikube https://minikube.sigs.k8s.io/docs/start/
  • Kind https://kind.sigs.k8s.io/docs/user/quick-start/

Install the ingress controller

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/baremetal/deploy.yaml

Install an application that will act as default backend (is just an echo app)

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/docs/examples/http-svc.yaml

Create an ingress (please add any additional annotation required)

echo "
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/permanent-redirect: https://example.com
  name: default-backend
  namespace: nginx-ingress
spec:
  defaultBackend:
    service:
      name: http-svc
      port:
        name: http
" | kubectl apply -f -

make a request

POD_NAME=$(k get pods -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx -o NAME)
kubectl exec -it -n ingress-nginx $POD_NAME -- curl -i localhost

kevincox avatar Jun 20 '22 20:06 kevincox

@kevincox: This issue is currently awaiting triage.

If Ingress contributors determines this is a relevant issue, they will accept it by applying the triage/accepted label and provide further guidance.

The triage/accepted label can be added by org members by writing /triage accepted in a comment.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

k8s-ci-robot avatar Jun 20 '22 22:06 k8s-ci-robot

/remove-kind bug I could not reproduce the problem

longwuyuan avatar Jun 20 '22 23:06 longwuyuan

Can you share the configuration you used?

kevincox avatar Jun 21 '22 00:06 kevincox

% cat ~/bin/create.www.dev.mydomain.com.sh kubectl create ns mydomain && kubectl -n mydomain create secret tls wildcard.dev.mydomain.com --cert ~/myclusters/letsencrypt/mydomain/dev.mydomain.com/fullchain1.pem --key ~/myclusters/letsencrypt/mydomain/dev.mydomain.com/privkey1.pem && kubectl -n mydomain create deploy frontend --image nginx:alpine --port 80 && kubectl -n mydomain expose deploy frontend --port 80 && sleep 3 && kubectl -n mydomain create ing frontend --class nginx --rule www.dev.mydomain.com/*=frontend:80,tls=wildcard.dev.mydomain.com

longwuyuan avatar Jun 21 '22 01:06 longwuyuan

That appears to be creating an ingress for a specific domain rather than for the default-backend. I also don't see where the redirect annotation is being applied in your script.

kevincox avatar Jun 21 '22 01:06 kevincox

The annotation is not limited for its use on default-backend. Since this issue is related to the annotation, its implied obviously that the annotation was used

% k  describe ing frontend
Name:             frontend
Labels:           <none>
Namespace:        mydomain
Address:          x.x.x.x
Ingress Class:    nginx
Default backend:  <default>
TLS:
  wildcard.dev.mydomain.com terminates www.dev.mydomain.com
Rules:
  Host                     Path  Backends
  ----                     ----  --------
  www.dev.mydomain.com  
                           /   frontend:80 (10.244.0.77:80)
Annotations:               nginx.ingress.kubernetes.io/permanent-redirect: https://www.google.com
Events:
  Type    Reason  Age                 From                      Message
  ----    ------  ----                ----                      -------
  Normal  Sync    3s (x3 over 2m45s)  nginx-ingress-controller  Scheduled for sync

longwuyuan avatar Jun 21 '22 01:06 longwuyuan

The annotation is still on an ingress with a specific host though, not the ingress for the default-backend.

kevincox avatar Jun 21 '22 01:06 kevincox

What is the image being used to create the pod at the backend and how did you create the svc for that pod ? Its not crystal clear as to what problem has to be solved here because I am able to get google.com landing page, even though I sent request to www.dev.mydomain.com.

Lets use the response codes etc below. Can you write what you expect to see different from what is seen below ;

HTTP/1.1 301 Moved Permanently
Date: Tue, 21 Jun 2022 01:47:27 GMT
Content-Type: text/html
Content-Length: 162
Connection: keep-alive
Location: https://www.google.com

HTTP/2 200 
content-type: text/html; charset=ISO-8859-1
p3p: CP="This is not a P3P policy! See g.co/p3phelp for more info."
date: Tue, 21 Jun 2022 01:47:27 GMT
server: gws
x-xss-protection: 0
x-frame-options: SAMEORIGIN
expires: Tue, 21 Jun 2022 01:47:27 GMT
cache-control: private
set-cookie: 1P_JAR=2022-06-21-01; expires=Thu, 21-Jul-2022 01:47:27 GMT; path=/; domain=.google.com; Secure
set-cookie: AEC=AakniGNTCUeIfP9DatS_09P2Oow_1s7QaNfok1mogjWjs3Mg3-7qDmzryQ; expires=Sun, 18-Dec-2022 01:47:27 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax
set-cookie: NID=511=dLeU0f3ISPgpZ6aJYFehBrFTy4hKzpT7InZprN4o8ZnQ2TUlBcP5v8MM2J7emxG8LrTSMzuJ80sQ_nttI4BelE_DFfp5ZbVAkb1V-vA4Fj-YIr2iEQKfbeBrqsN5W15h3p6IOUi3aTvEvn2u9xEkdw0pNHh39sxirCjRB8Jbk-U; expires=Wed, 21-Dec-2022 01:47:27 GMT; path=/; domain=.google.com; HttpOnly
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"

Maybe it will be better, if you can write down steps by step instructions, that someone can copy/paste on their laptop and reproduce the problem. Make sure to provide exact copy/paste ease even for the backend pod, svc and ingress creation etc etc. You can just do http instead of https.

longwuyuan avatar Jun 21 '22 01:06 longwuyuan

See the manifests in the original post. Everything is there.

It is using the built-in default service, but I don't think that should even matter because it should never actually be hit with the redirect configured.

Can you write what you expect to see different from what is seen below

The response you provide looks correct but is for a different configuration.

kevincox avatar Jun 21 '22 10:06 kevincox

I think this is working as intended (I'm not a maintainer of this repo though and they might have a different say) and the reasoning could be found in the issue that you linked to (#1406, comment):

"This requirement makes sure you only get a redirect for the rule where the annotation is configured and not all the paths"

To elaborate, what I believe is supposed to happen is that nginx.ingress.kubernetes.io/permanent-redirect should only be applied when the path that you made the request to actually matches one of the rules of any the Ingress resource.

If this were not the case, it would be ambiguous as to where a request like curl INGRESS_IP -H "Host: abc.xyz" should be sent to in the following case:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-1
  annotations:
    nginx.ingress.kubernetes.io/permanent-redirect: https://example.com
spec:
  ingressClassName: nginx
  defaultBackend:
    service:
      name: default-backend
      port:
        name: http
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-2
spec:
  ingressClassName: nginx
  rules:
  - host: 'foo.bar.io'
    http:
      paths:
      - backend:
          service:
            name: foo-bar-service
            port:
              number: 80
        path: /
        pathType: Prefix
  defaultBackend:
    service:
      name: default-backend
      port:
        name: http
---

gauravkghildiyal avatar Jun 28 '22 05:06 gauravkghildiyal

The case you provided is basically ambiguous anyways. I think the kube Ingress is intended for a single IP per Ingress which ingress-nginx breaks. However in the case where you don't have multiple ingresses the custom snippet works so I don't see why the nginx.ingress.kubernetes.io/permanent-redirect rule would be any different. I would expect it to basically be a safe short-form for the redirects.

the reasoning could be found in the issue that you linked to (https://github.com/kubernetes/ingress-nginx/issues/1406, https://github.com/kubernetes/ingress-nginx/issues/1405#issuecomment-333336305):

This seems to have a significant difference to me. In that case there is a host with no backend services which is very strange. In this case we have a default service which works for every path there.

kevincox avatar Jun 28 '22 23:06 kevincox

I will let @aledbf verify the intended behaviour, since not allowing redirects to work in this was was explicitly disabled in #1768

And seeing this behaviour being forced explicitly, I feel that the fact you are using configuration-snippet to redirect is more of a hack and not the expected usage.

gauravkghildiyal avatar Jun 29 '22 04:06 gauravkghildiyal

I have the same problem with nginx.ingress.kubernetes.io/app-root

I wanted to create an ingress that does 2 things: redirect / to /approot, and provide a defaultbackend (for custom 404s). I tried

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: root-ingress
  annotations:
    nginx.ingress.kubernetes.io/app-root: /rootapp
spec:
  defaultBackend:
    service:
      name: default-backend
      port:
        number: 80

but I have to add

  # Needed for app-root redirect to work
  rules:
  - http:
      paths:
      - path: /
        pathType: Exact
        backend:
          service:
            name: default-backend
            port:
              number: 80

I have to reuse default-backend in my rules even though it will never be used, just because validation fails otherwise. Are there any plans to simplify this ?

jonenst avatar Sep 13 '22 12:09 jonenst

Setting app-root and doing a redirect are 2 different functionalities. There is not enough information or justification or even a reasonable use-case description with regards to what problem you need to solve in your infrastructure and what problem exists in the ingress-nginx-controller.

I tested the app-root annotation and it works. I tested the redirect annotation and that too works. So even the original problem description does is not backed by valid information & justification for the expectation and neither is the most recent post here backed by enough information or justification to explain what seems to be a problem (that needs to be solved) in the controller.

To the state the obvious, If you send a request to a cluster, the controller attempts to matche the request to a rule and upon failure to match ANY rule, the default backend sends a response. So for the original problem in the first post, the expectation elaborated is like ;

  • invalid or bad request is sent to controller
  • default-backend's response is acknowledged as the designed behaviour because request did not match ANY rule
  • but instead of default-backend sending a response to client who sent the request, the reporting user is expecting a redirect to example.com

The default-backend configured there is not coded to do a redirect when a client request from outside the cluster does not match any rule. Its designed to send a response to the client.

Now as for the latest post above by @jonenst , the annotation app-root is designed to go to the value configured in that annotation, in the context of the service (and ultimately the pod) that is configured in the ingress.spec.rules.http.paths.backend field. So some useful relvant information and justification is needed here to explain why there is an expectation that the app-root annotation should consider the field ingress.spec.defaultBackend, to even begin with. That field is there to deal with the case where the client request did not match any rule at all.

longwuyuan avatar Sep 13 '22 18:09 longwuyuan

% k explain ingress.spec.defaultBackend KIND: Ingress VERSION: networking.k8s.io/v1

RESOURCE: defaultBackend <Object>

DESCRIPTION: DefaultBackend is the backend that should handle requests that don't match any rule. If Rules are not specified, DefaultBackend must be specified. If DefaultBackend is not set, the handling of requests that do not match any of the rules will be up to the Ingress controller.

longwuyuan avatar Sep 13 '22 18:09 longwuyuan

hi @longwuyuan, thanks for taking the time to think about this. I can elaborate on the use case if you want. I can also probably write a repro repo if you need.

Context of my use case: I have several "apps" that work together. They each have an ingress with one rule that uses a prefix for the app (so the user visits app.com/app1/ or app.com/app2/).

Use case: implement the following:

  • Users can also just visit app.com/, and need to be redirected to app.com/app1/.
  • For users that mistakenly visit app.com/notapp1/, I want to display a custom 404.

I thought creating an ingress with the app-root annotation would solve the first requirement, so I created:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: root-ingress
  annotations:
    nginx.ingress.kubernetes.io/app-root: /app1

I couldn't kubectl apply this because it failed validation: you need either rules or a defaultBackend I tried adding as little as I could, but needed to add a full rule sending to a service (like in https://kubernetes.github.io/ingress-nginx/examples/rewrite/#app-root what is http-svc here, does it exist ? why is it required ?):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: root-ingress
  annotations:
    nginx.ingress.kubernetes.io/app-root: /app1
  rules:
  - http:
      paths:
      - path: /
        pathType: Exact
        backend:
          service:
            name: notimportant # here I can use a non existing (which will warn in the nginx controller logs or an existing service which is confusing, we don't care anyway since app-root redirects instead of calling the service
            port:
              number: 80

Then I thought that implementing the 404 requirement would allow me to delete the rule with the unused service, because I would add a defaultBackend anyway:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: root-ingress
  annotations:
    nginx.ingress.kubernetes.io/app-root: /rootapp
spec:
  defaultBackend:
    service:
      name: default-backend
      port:
        number: 80

default-backend sends 404s to any request, so visiting app.com/notapp correctly returns a 404, but now 'app.com/' doesn't redirect on 'app.com/app1'. I had to add the rule again, and so I used the default-backend service in the rule just because it exists, and doesn't produce any warnings:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: root-ingress
  annotations:
    nginx.ingress.kubernetes.io/app-root: /rootapp
spec:
  defaultBackend:
    service:
      name: default-backend
      port:
        number: 80
  # Needed for app-root redirect to work
  rules:
  - http:
      paths:
      - path: /
        pathType: Exact
        backend:
          service:
            name: default-backend
            port:
              number: 80

The configuration I gave with only app-root and a defaultBackend felt very intuitive to me. It didn't feel intuitive that the app-root annotation only works if at least one rule matches, although now I see that it could be a reasonable behavior. Maybe that's just a documentation issue ?

Let me know what you think, or if you want me to do anything else. Jon

jonenst avatar Sep 13 '22 18:09 jonenst

This is not a bug. This is related to using the annotations and features. There are not many resources to track support issues here on github. Please come to kubernetes slack and discuss this there because there are several great folks there who like to help and have the knowledge. You may even find someone who has the same use case.

From what you have discussed in the post above, its just going to be a long elaborate meticulous discussion, which will be fruitful only if this entire topic is broken down into a list of http requests you send and the behaviour you expect. On slack you should post that list for others to understand the problem you are trying to solve in your infrastructure.

Personally, I don't think you need the approot annotation. A simple ingress can define a rule that takes hostname with path as /, with pathType as Exact and backend as app1.

longwuyuan avatar Sep 14 '22 13:09 longwuyuan

Hi @longwuyuan, thanks again for you time. Feel free to close this issue if it's not the right place to discuss. Because this is not a blocking problem, I don't want to impose more burden on the limited resources.

Regarding the approot annotation, in my usecase, I need the users to have the url change in their browser, not serving the app from / so I don't think that what you are suggesting would fulfil that requirement.

jonenst avatar Sep 14 '22 14:09 jonenst

Hi @jonenst The idea is to discuss on slack as there are more people there and then update issue here if you find data on a bug or a problem in the controller.

There are several factors and tiny details that are not in the discussion. For example you can redirect one request but if all your requests contain hostname + path combo that needs a redirection, then its a different story.

Please discuss in the ingress-nginx-users channel in slack.

longwuyuan avatar Sep 14 '22 14:09 longwuyuan

The Kubernetes project currently lacks enough contributors to adequately respond to all issues and PRs.

This bot triages issues and PRs according to the following rules:

  • After 90d of inactivity, lifecycle/stale is applied
  • After 30d of inactivity since lifecycle/stale was applied, lifecycle/rotten is applied
  • After 30d of inactivity since lifecycle/rotten was applied, the issue is closed

You can:

  • Mark this issue or PR as fresh with /remove-lifecycle stale
  • Mark this issue or PR as rotten with /lifecycle rotten
  • Close this issue or PR with /close
  • Offer to help out with Issue Triage

Please send feedback to sig-contributor-experience at kubernetes/community.

/lifecycle stale

k8s-triage-robot avatar Dec 13 '22 15:12 k8s-triage-robot

The original issue description here uses the phrase "default-backend" and claims that permanent-redirect annotation does not work.

I have tested both default-backend feature and the permanent-redirect annotation. Both work fine on their own. So after all the discussion done on this issue, it is safe to assume that there is some kind of assumption like a workload is deemed the default-backend first (which is supposed to contain custom error handling as that is what a default -backend or a custom-backend would do). And then on top of that effort for custom-error-handling, the traffic that heads to that default-backend is expected to be permanently redirected to google.com.

This is not a real use case in practical terms and also unclear in its description of reproducing it. I will close the issue as it is not tracked actively and adding up to the could of open issues without having a real action-item on it for the project.

Thanks

/close

longwuyuan avatar May 03 '24 07:05 longwuyuan

@longwuyuan: Closing this issue.

In response to this:

The original issue description here uses the phrase "default-backend" and claims that permanent-redirect annotation does not work.

I have tested both default-backend feature and the permanent-redirect annotation. Both work fine on their own. So after all the discussion done on this issue, it is safe to assume that there is some kind of assumption like a workload is deemed the default-backend first (which is supposed to contain custom error handling as that is what a default -backend or a custom-backend would do). And then on top of that effort for custom-error-handling, the traffic that heads to that default-backend is expected to be permanently redirected to google.com.

This is not a real use case in practical terms and also unclear in its description of reproducing it. I will close the issue as it is not tracked actively and adding up to the could of open issues without having a real action-item on it for the project.

Thanks

/close

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

k8s-ci-robot avatar May 03 '24 07:05 k8s-ci-robot