NGINX Integration

This guide explains how to integrate heimdall with NGINX as well as with the NGINX Ingress Controller.

NGINX is an HTTP and reverse proxy server which became famous as one of the fastest web servers out there, heimdall can be integrated with by making use of the ngx_http_auth_request_module. In such setup, NGINX delegates authentication and authorization to heimdall. If heimdall answers with a 2XX code, NGINX grants access and forwards the original request to the upstream service. If heimdall returns 401 or 403, the access is denied with the corresponding error code. Any other response code returned by heimdall is considered an error.

Prerequisites

Limitations

NGINX ngx_http_auth_request_module responsible for communication with external authentication & authorization services, like heimdall, has a few limitations. As written above, it only supports 200, 401 and 403 response codes. That means:

  • You’ll not be able to drive redirects from heimdall, as 3xx error codes will result in 500 error returned by NGINX. You can partially overcome that limitation by letting heimdall respond with a 401 or 403 error code and mapping that to a redirect in NGINX itself, e.g. like shown below. This definitely not DRY and will not allow you using multiple identity provider if you need to

    # nginx.conf
    ...
    
    # if the ext auth server, like heimdall returns `401 not authorized`
    # then forward the request to the error401 block
    error_page 401 = @error401;
    
    location @error401 {
      # redirect to the IdP for login
      return 302 https://your-idp-service/login;
      # you usually want your IdP to redirect back upon successful authentication
      # typically, you can achieve that by adding such query parameters like
      # return_to set to the value of the current request
    }
  • If there is no matching rule on heimdall side, heimdall responds with 404 Not Found, which, as said above will be treated by NGINX as error. To avoid such situations, you can define a default rule, which is anyway recommended to have secure defaults

Vanilla NGINX

Since NGINX is highly configurable and heimdall supports different integration options, you can use any of the configuration examples given below. All of these enable heimdall to build the URL of the protected backend server for rule matching purposes.

In most cases you must configure heimdall to trust your NGINX instances by setting trusted_proxies for the Decision, respectively Proxy service. Exceptions are described in the sections below.

Forward only the path and query information

With this method you don’t set any headers. That means, you cannot rely on the used HTTP scheme, or the host and port in your rules. Here NGINX uses the same HTTP method, used in the original request to it and add the path and query to the path/query URL used for communication with heimdall. That integration method does not require the configuration of trusted_proxies in heimdall.

# nginx.conf
...

location / {
  auth_request             /_auth;  (1)
  auth_request_set         $auth_cookie $upstream_http_set_cookie;  (2)
  add_header               Set-Cookie $auth_cookie;
  auth_request_set         $authHeader0 $upstream_http_authorization;  (3)
  proxy_set_header         'Authorization' $authHeader0;
  # mitigate HTTPoxy Vulnerability
  # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/
  proxy_set_header Proxy   "";
  ...
}

location = /_auth {  (4)
  internal;
  access_log               off;
  proxy_method             $request_method; (5)
  proxy_pass               http://heimdall:4456$request_uri; (6)
  proxy_pass_request_body  off; (7)
  proxy_set_header         Content-Length   "";
  proxy_set_header         Host $http_host; (8)
}
1Configures NGINX to forward every request to the internal /_auth endpoint (this is where the actual heimdall integration happens - see below).
2When the response from heimdall returns, this and the next line set the Cookies set by heimdall in the response (whether you need this depends on your Contextualizers and Finalizers configuration)
3When the response from heimdall returns, this and the next line set the Authorization header set by heimdall in the response (which header to set depends again on your Contextualizers and Finalizers configuration)
4This is where the "magic" happens
5Configure NGINX to use the HTTP method used by its client. Without this setting the implementation of proxy_path will use the HTTP GET method.
6Configures NGINX to pass the request to heimdall and sets the request path and queries from the original request
7Disables sending of the request body. If your heimdall rules make use of the body, you should set this to on and remove the next line.
8Lets the NGINX setting the Host header, so it is accessible to heimdall.

Forward all information in X-Forwarded-* headers

With this method you set the X-Forwarded-Method, X-Forwarded-Proto, X-Forwarded-Host and X-Forwarded-Uri to let heimdall know the host, respectively domain url and the used HTTP method.

Compared to the previous integration option, the configuration only differs in the definition of the internal /_auth endpoint. So, the example configuration is limited to that part only.

Proper configuration of trusted_proxies is mandatory if using this option.
# nginx.conf
...

location = /_auth {
  internal;
  proxy_pass               http://heimdall:4456;  (1)
  proxy_pass_request_body  off;
  proxy_set_header         Content-Length         "";
  proxy_set_header         X-Forwarded-Method     $request_method;  (2)
  proxy_set_header         X-Forwarded-Proto      $scheme;  (3)
  proxy_set_header         X-Forwarded-Host       $http_host;  (4)
  proxy_set_header         X-Forwarded-Uri        $request_uri;  (5)
  proxy_set_header         X-Forwarded-For        $remote_addr;
}
1Configures NGINX to pass the request to heimdall.
2Let NGINX forward the used HTTP method to heimdall.
3Let NGINX forward the used HTTP scheme to heimdall.
4Let NGINX forward the used host to heimdall.
5Let NGINX forward the used path and query parameter to heimdall.

NGINX Ingress Controller

Global Configuration

Using X-Forwarded-* headers

The configuration used in the example below requires proper configuration of trusted_proxies on heimdall side.

Global configuration can be achieved by setting the following properties in controller ConfigMap. If you install the NGINX controller via the helm chart, you can add these properties under the controller.config property of your helm values.yaml file.

global-auth-url: "http://<heimdall service name>.<namespace>.svc.cluster.local:<decision port>" (1)
global-auth-response-headers: Authorization (2)
global-auth-snippet: | (3)
  proxy_set_header    X-Forwarded-Method   $request_method;
  proxy_set_header    X-Forwarded-Proto    $scheme;
  proxy_set_header    X-Forwarded-Host     $http_host;
  proxy_set_header    X-Forwarded-Uri      $request_uri;
1Configures the controller to use heimdall’s decision service endpoint with <heimdall service name>, <namespace> and <decision port> depending on your configuration.
2Let NGINX forward the Authorization header set by heimdall to the upstream service upon successful response. This configuration depends on your Contextualizers and Finalizers configuration. If not configured, NGINX will only react on Set-Cookie headers in responses from heimdall by default.
3Configures the required headers to pass the information about the used HTTP scheme, host and port, request path and used query parameters to be forwarded to heimdall.
Without that, heimdall will not be able extracting relevant information from the NGINX request as it does not support NGINX proprietary X-Original-Method and X-Original-Uri used by it for the same purposes.

With that in place, you can simply use the standard Ingress resource, and the NGINX Ingress Controller will ensure, each request will be analyzed by heimdall first.

This will result in an NGINX configuration corresponding to the integration option, described in the Forward all information in X-Forwarded-* headers section.

Alternative Configuration

Alternatively, if you don’t want configuring trusted_proxies and do not rely on the used HTTP scheme, host and port in your rules, you can also use the location-snippet and the server-snippet to the ConfigMap of the NGINX Ingress Controller with values shown below.

This example is an exact copy of the configuration used in the very first integration option described above.

location-snippet: |
  auth_request               /_auth;
  auth_request_set           $auth_cookie $upstream_http_set_cookie;
  add_header                 Set-Cookie $auth_cookie;
  auth_request_set           $auth_header $upstream_http_authorization;
  proxy_set_header           'Authorization' $auth_header;
  proxy_set_header Proxy     "";
server-snippet: |
  location = /_auth {
    internal;
    access_log               off;
    proxy_method             $request_method;
    proxy_pass               http://<heimdall service name>.<namespace>.svc.cluster.local:<decision port>$request_uri;
    proxy_pass_request_body  off;
    proxy_set_header         Content-Length   "";
    proxy_set_header         Host $http_host;
  }

As with the previous integration option, you can add these properties under the controller.config property of your helm values.yaml file if you install the NGINX Ingress Controller via helm.

Integration on Ingress Resource Level

Using X-Forwarded-* headers

One option to integrate heimdall with the NGINX Ingress Controller on the Ingress resource level is making use of the nginx.ingress.kubernetes.io/auth-url, nginx.ingress.kubernetes.io/auth-response-headers and the nginx.ingress.kubernetes.io/auth-snippet annotation as shown in the example below. This approach requires proper configuration of trusted_proxies on heimdall side. On NGINX Ingress Controller side you must allow the usage of nginx.ingress.kubernetes.io/auth-snippet (See also here).

nginx.ingress.kubernetes.io/auth-url: "http://<heimdall service name>.<namespace>.svc.cluster.local:<decision port>"
nginx.ingress.kubernetes.io/auth-response-headers: Authorization
nginx.ingress.kubernetes.io/auth-snippet: |
  proxy_set_header    X-Forwarded-Method   $request_method;
  proxy_set_header    X-Forwarded-Proto    $scheme;
  proxy_set_header    X-Forwarded-Host     $http_host;
  proxy_set_header    X-Forwarded-Uri      $request_uri;
# other annotations required

Alternative Configuration

Alternatively, if you don’t want configuring trusted_proxies and do not rely on the used HTTP scheme, host and port in your rules, you can also use the nginx.ingress.kubernetes.io/configuration-snippet and nginx.ingress.kubernetes.io/server-snippet annotations and use the configuration shown below.

This example is an exact copy of the configuration used in the very first integration option described above.

nginx.ingress.kubernetes.io/configuration-snippet: |
  auth_request               /_auth;
  auth_request_set           $auth_cookie $upstream_http_set_cookie;
  add_header                 Set-Cookie $auth_cookie;
  auth_request_set           $auth_header $upstream_http_authorization;
  proxy_set_header           'Authorization' $auth_header;
  proxy_set_header Proxy     "";
nginx.ingress.kubernetes.io/server-snippet: |
  location = /_auth {
    internal;
    access_log               off;
    proxy_method             $request_method;
    proxy_pass               http://<heimdall service name>.<namespace>.svc.cluster.local:<decision port>$request_uri;
    proxy_pass_request_body  off;
    proxy_set_header         Content-Length   "";
    proxy_set_header         Host $http_host;
  }
# other annotations required

Additional Resources

Checkout the examples on GitHub for a working demo.

Last updated on May 17, 2024