Nginx - NTLM module

In my previous post Nginx - Custom upstream module I described in detail how you can develop your own custom nginx upstream module. You better check that first because I will refer it a lot.

TL;DR

Check out this github.com/gabihodoroaga/nginx-ntlm-module repository if you want to get started with a custom nginx upstream module.

The NTLM module

So, I was looking for a solution to configure a reverse proxy that supports NTLM authentication passthrough, and because this is not available unless you have a commercial subscription to Nginx, I thought to develop my own custom module.

This NTLM module allows proxying requests with NTLM Authentication. The upstream connection is bound to the client connection once the client sends a request with the “Authorization” header field value starting with “Negotiate” or “NTLM”. Further client requests will be proxied through the same upstream connection, keeping the authentication context.

In order to achieve this we need to add more code to our startup custom module.

First, we need to define our configuration struct to allow us to keep the information about maximum number of the upstream connections, the keepalive timeout and the 2 queues to keep track of cached and free connection pairs.

typedef struct {
  ngx_uint_t max_cached;
  ngx_msec_t timeout;
  ngx_queue_t free;
  ngx_queue_t cache;
  ngx_http_upstream_init_pt original_init_upstream;
  ngx_http_upstream_init_peer_pt original_init_peer;
} ngx_http_upstream_ntlm_srv_conf_t;

Next we need a struct to hold our pair of connections between client and the server

typedef struct {
  ngx_http_upstream_ntlm_srv_conf_t *conf;
  ngx_queue_t queue;
  ngx_connection_t *peer_connection;
  ngx_connection_t *client_connection;
} ngx_http_upstream_ntlm_cache_t;

and also the peer data struct need to be adjusted a little bit


typedef struct {
  ngx_http_upstream_ntlm_srv_conf_t *conf;
  ngx_http_upstream_t *upstream;
  void *data;
  ngx_connection_t *client_connection;
  unsigned cached : 1;
  unsigned ntlm_init : 1;
  ngx_event_get_peer_pt original_get_peer;
  ngx_event_free_peer_pt original_free_peer;
#if (NGX_HTTP_SSL)
  ngx_event_set_peer_session_pt original_set_session;
  ngx_event_save_peer_session_pt original_save_session;
#endif

} ngx_http_upstream_ntlm_peer_data_t;

After that we need to adjust module commands array in order to handle the ntlm_timeout directive:

static ngx_command_t ngx_http_upstream_ntlm_commands[] = {

    {ngx_string("ntlm"), NGX_HTTP_UPS_CONF | NGX_CONF_NOARGS | NGX_CONF_TAKE1,
     ngx_http_upstream_ntlm, NGX_HTTP_SRV_CONF_OFFSET, 0, NULL},

    {ngx_string("ntlm_timeout"), NGX_HTTP_UPS_CONF | NGX_CONF_TAKE1,
     ngx_conf_set_msec_slot, NGX_HTTP_SRV_CONF_OFFSET,
     offsetof(ngx_http_upstream_ntlm_srv_conf_t, timeout), NULL},

    ngx_null_command /* command termination */
};

Now we need to update the ngx_http_upstream_init_ntlm_peer function to check the headers of the client request to see if the Authorization header exists and the value begin with “NTLM” or “Negotiate”

....
  if (r->headers_in.authorization != NULL) {
    auth_header_value = r->headers_in.authorization->value;

    if ((auth_header_value.len >= sizeof("NTLM") - 1 &&
         ngx_strncasecmp(auth_header_value.data, (u_char *)"NTLM",
                         sizeof("NTLM") - 1) == 0) ||
        (auth_header_value.len >= sizeof("Negotiate") - 1 &&
         ngx_strncasecmp(auth_header_value.data, (u_char *)"Negotiate",
                         sizeof("Negotiate") - 1) == 0)) {
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "ntlm auth header found");
      hnpd->ntlm_init = 1;
    }
  }
....  

In the next function ngx_http_upstream_get_ntlm_peer we will make the magic happen. Before we will return the control to the original peer we check to see if we have already cached the client connection and the associated upstream connection.

...

/* search cache for suitable connection */

  cache = &hndp->conf->cache;

  for (q = ngx_queue_head(cache); q != ngx_queue_sentinel(cache);
       q = ngx_queue_next(q)) {
    item = ngx_queue_data(q, ngx_http_upstream_ntlm_cache_t, queue);
    if (item->client_connection == hndp->client_connection) {
      c = item->peer_connection;
      ngx_queue_remove(q);
      ngx_queue_insert_head(&hndp->conf->free, q);
      hndp->cached = 1;
      goto found;
    }
  }

  return NGX_OK;

found:

  ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                 "get ntlm peer: using connection %p", c);

  c->idle = 0;
  c->sent = 0;
  c->data = NULL;
  c->log = pc->log;
  c->read->log = pc->log;
  c->write->log = pc->log;
  c->pool->log = pc->log;

  if (c->read->timer_set) {
    ngx_del_timer(c->read);
  }

  pc->connection = c;
  pc->cached = 1;

  return NGX_DONE;

...

and in the next function ngx_http_upstream_free_ntlm_peer we will add the code for caching the client and the upstream connection pair.

...
  ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                 "free ntlm peer: saving connection %p", c);

  if (ngx_queue_empty(&hndp->conf->free)) {
    q = ngx_queue_last(&hndp->conf->cache);
    ngx_queue_remove(q);

    item = ngx_queue_data(q, ngx_http_upstream_ntlm_cache_t, queue);
    ngx_http_upstream_ntlm_close(item->peer_connection);

  } else {
    q = ngx_queue_head(&hndp->conf->free);
    ngx_queue_remove(q);
    item = ngx_queue_data(q, ngx_http_upstream_ntlm_cache_t, queue);
  }

  ngx_queue_insert_head(&hndp->conf->cache, q);

...

also in this function ngx_http_upstream_free_ntlm_peer we need to add a cleanup handler to be able remove the client connection from the cache and to close the upstream connection when client drops connection

...
    if (cleanup_item == NULL) {
        cln = ngx_pool_cleanup_add(item->client_connection->pool, 0);
        if (cln == NULL) {
            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                           "ntlm free peer ngx_pool_cleanup_add returned null");
        } else {
            cln->handler = ngx_http_upstream_client_conn_cleanup;
            cln->data = item;
        }
    }
...

Maybe this it seems that is making a lot of sense right now but if you study the full module source code you will understand.

Just download the sources form here github.com/gabihodoroaga/nginx-ntlm-module and build it yourself.

Disclaimer

My intention with this module was not to create a full replica of the original commercial Nginx NTLM module. I created this because I was not able to find a free alternative for development purposes.

So, you can use Nginx Plus, or you can try this one.


nginxc

763 Words

2020-10-11 20:22 +0000