Ingress-nginx: Nginx does not return http auth service response body in the case of 401 error

Created on 4 Apr 2018  路  13Comments  路  Source: kubernetes/ingress-nginx

NGINX Ingress controller version: 0.12.0

What happened:
Nginx returns custom error page instead of external http auth service response body when auth service return 401 status code with json response body. Is it possible to return auth service response body in the case of auth errors except 2xx status codes?

Nginx response when got 401 error:

<html>
    <head>
        <title>401 Authorization Required</title>
    </head>
    <body bgcolor="white">
        <center>
            <h1>401 Authorization Required</h1>
        </center>
        <hr>
        <center>nginx/1.13.9</center>
    </body>
</html>

Nginx conf:

ingress.kubernetes.io/ssl-redirect: "false"
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/auth-method: "POST"
nginx.ingress.kubernetes.io/service-upstream: "true"
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/auth-url: "http://auth-svc.default.svc.cluster.local/api/auth"
nginx.ingress.kubernetes.io/auth-response-headers: UserRole

What you expected to happen:
I expect to be able to return auth service response body to client when auth service gives error unless the status code is 2xx.

Most helpful comment

Fom Nginx auth_request documentation:

If the subrequest returns a 2xx response code, the access is allowed. If it returns 401 or 403, the access is denied with the corresponding error code. Any other response code returned by the subrequest is considered an error.

However I was able to create custom errors with auth_request using the following configuration:

location = /error/401 {
        internal;
        proxy_method GET;
        proxy_set_header x-code 401;
        proxy_pass http://custom-default-backend;
}
location = /error/403 {
        internal;
        proxy_method GET;
        proxy_set_header x-code 403;
        proxy_pass http://custom-default-backend;
}
location = /error/500 {
        internal;
        proxy_method GET;
        proxy_set_header x-code 500;
        proxy_pass http://custom-default-backend;
}

# Your authenticated location, just an example to be simple
location ~ ^/(.*)$ {
        auth_request /auth;
        error_page 401 =401 /error/401;
        error_page 403 =403 /error/403;
        error_page 500 =500 /error/500;
        # proxy_pass and etc...
}

But with this your errors (401, 403, 500) will ALWAYS come from custom locations. And still it is not possible to have the auth_request response body.

It seems to be a limitation of Nginx. Has anyone got around to this?

All 13 comments

same issue here, I need to get whatever response body is generated by auth service but cannot get dynamically. It would be very helpful if there is any workaround for solving the problem

Fom Nginx auth_request documentation:

If the subrequest returns a 2xx response code, the access is allowed. If it returns 401 or 403, the access is denied with the corresponding error code. Any other response code returned by the subrequest is considered an error.

However I was able to create custom errors with auth_request using the following configuration:

location = /error/401 {
        internal;
        proxy_method GET;
        proxy_set_header x-code 401;
        proxy_pass http://custom-default-backend;
}
location = /error/403 {
        internal;
        proxy_method GET;
        proxy_set_header x-code 403;
        proxy_pass http://custom-default-backend;
}
location = /error/500 {
        internal;
        proxy_method GET;
        proxy_set_header x-code 500;
        proxy_pass http://custom-default-backend;
}

# Your authenticated location, just an example to be simple
location ~ ^/(.*)$ {
        auth_request /auth;
        error_page 401 =401 /error/401;
        error_page 403 =403 /error/403;
        error_page 500 =500 /error/500;
        # proxy_pass and etc...
}

But with this your errors (401, 403, 500) will ALWAYS come from custom locations. And still it is not possible to have the auth_request response body.

It seems to be a limitation of Nginx. Has anyone got around to this?

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.

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

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

@fejta-bot: Closing this issue.

In response to this:

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

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.

I expect to be able to return auth service response body to client when auth service gives error unless the status code is 2xx.

Exactly what we need as well...

has anyone found an alternative solution?

I needed this as well, but I found a workaround by using the nginx.ingress.kubernetes.io/auth-signin to redirect to a page with the error response body. For example, my authenticate endpoint is /auth/authenticate, my ingress annotations are:

nginx.ingress.kubernetes.io/auth-url: <internal host>/auth/authenticate
nginx.ingress.kubernetes.io/auth-signin: /auth/authenticate

It seems that it converts all requests into GET requests but preserves the headers, which means that you just have to support GET requests (and reuse the auth-url endpoint).

/reopen
/remove-lifecycle rotten

This is still an issue with v0.25.0. It would be great if we could return the auth service's body as the result instead of requiring custom error pages or redirects.

@bekriebel: You can't reopen an issue/PR unless you authored it or you are a collaborator.

In response to this:

/reopen
/remove-lifecycle rotten

This is still an issue with v0.25.0. It would be great if we could return the auth service's body as the result instead of requiring custom error pages or redirects.

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.

I'm using Nginx 1.17 and found this limitation as well. If I access the upstream auth server directly I could see the 401 status with response content. But the custom response body served by the upstream server gets overwritten by the default Nginx 401 page through the auth_request proxy.

For anyone else running into this, here's my workaround. It's a big of a config kludge but it solves the problem of returning the auth backend response to the client and relies _almost_ entirely on annotations in ingress configuration. The one exception is the proxy_pass directive in the error location; it uses the $target variable set in the generated auth location block to point to the auth backend. That's very much an implementation detail internal to the ingress machinery so be aware that this could break in the future if someone renames that variable.

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: my-ingress
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/auth-url: http://auth-service.namespace.svc.cluster.local/authorize
    nginx.ingress.kubernetes.io/auth-response-headers: |
      X-My-Header,
      X-My-Other-Header,
      X-Yet-Another-Header
    nginx.ingress.kubernetes.io/server-snippet: |
      location /error/401 {
        internal;
        proxy_method GET;
        proxy_pass $target;
      }
    nginx.ingress.kubernetes.io/configuration-snippet: |
      error_page 401 =401 /error/401;
spec:
  rules:
    ...
nginx.ingress.kubernetes.io/server-snippet: |
  location /authz {
    internal;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Request-Id $req_id;
    proxy_set_header X-Request-Origin-Host $host;
    proxy_set_header X-Request-Origin-Path $request_uri;
    proxy_set_header X-Request-Origin-Method $request_method;
    #proxy_set_header Connection "Upgrade";
    #proxy_set_header Upgrade $http_upgrade;
    #proxy_http_version 1.1;
    proxy_method GET;
    proxy_pass http://vsc-leo2go-0.vsc-me-dev.ws03.svc.cluster.local;
  }
nginx.ingress.kubernetes.io/configuration-snippet: |
  access_by_lua '
    local res = ngx.location.capture("/authz")
    if res.status >= ngx.HTTP_OK and res.status < ngx.HTTP_SPECIAL_RESPONSE then
      ngx.req.set_header("X-Request-User-KID", res.header["X-Request-User-KID"])
      ngx.req.set_header("X-Request-Role-KID", res.header["X-Request-Role-KID"])
      return
    --elseif res.status == ngx.HTTP_UNAUTHORIZED then
    --  ngx.redirect("http://sso.sims-cn.com?redirect=")
    --  return
    end
    ngx.header.content_type = res.header["Content-Type"]
    ngx.status = res.status
    ngx.say(res.body)
    ngx.exit(res.status)
  ';
Was this page helpful?
0 / 5 - 0 ratings