Implement a per-process cache for increased performance

A configured number of cached (server,username,password) entries is held
for a configured amount of time to avoid repeated authentications for
each of several requests quickly following each other.
main
Jiri Hruska 11 years ago
parent bd4959918c
commit 8b2dcc7522
  1. 280
      ngx_http_auth_ldap_module.c

@ -27,6 +27,7 @@
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_md5.h>
#include <ldap.h>
typedef struct {
@ -49,6 +50,9 @@ typedef struct {
typedef struct {
ngx_array_t *servers; /* array of ngx_http_auth_ldap_server_t */
ngx_flag_t cache_enabled;
ngx_msec_t cache_expiration_time;
size_t cache_size;
} ngx_http_auth_ldap_main_conf_t;
typedef struct {
@ -56,7 +60,29 @@ typedef struct {
ngx_array_t *servers; /* array of ngx_http_auth_ldap_server_t* */
} ngx_http_auth_ldap_loc_conf_t;
typedef struct {
uint32_t small_hash; /* murmur2 hash of username ^ &server */
uint32_t outcome; /* 0 = authentication failed, 1 = succeeded */
ngx_msec_t time; /* ngx_current_msec when created */
u_char big_hash[16]; /* md5 hash of (username, server, password) */
} ngx_http_auth_ldap_cache_elt_t;
typedef struct {
ngx_http_auth_ldap_cache_elt_t *buckets;
ngx_uint_t num_buckets;
ngx_uint_t elts_per_bucket;
ngx_msec_t expiration_time;
} ngx_http_auth_ldap_cache_t;
typedef struct {
ngx_http_auth_ldap_cache_elt_t *cache_bucket;
u_char cache_big_hash[16];
uint32_t cache_small_hash;
} ngx_http_auth_ldap_ctx_t;
static void * ngx_http_auth_ldap_create_main_conf(ngx_conf_t *cf);
static char * ngx_http_auth_ldap_init_main_conf(ngx_conf_t *cf, void *parent);
static ngx_int_t ngx_http_auth_ldap_init_worker(ngx_cycle_t *cycle);
static char * ngx_http_auth_ldap_ldap_server_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char * ngx_http_auth_ldap_parse_url(ngx_conf_t *cf, ngx_http_auth_ldap_server_t *server);
static char * ngx_http_auth_ldap_parse_require(ngx_conf_t *cf, ngx_http_auth_ldap_server_t *server);
@ -69,10 +95,13 @@ static char * ngx_http_auth_ldap_merge_loc_conf(ngx_conf_t *, void *, void *);
static ngx_int_t ngx_http_auth_ldap_authenticate_against_server(ngx_http_request_t *r, ngx_http_auth_ldap_server_t *server,
ngx_http_auth_ldap_loc_conf_t *conf);
static ngx_int_t ngx_http_auth_ldap_set_realm(ngx_http_request_t *r, ngx_str_t *realm);
static ngx_int_t ngx_http_auth_ldap_authenticate(ngx_http_request_t *r, ngx_http_auth_ldap_loc_conf_t *conf);
static ngx_int_t ngx_http_auth_ldap_authenticate(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx,
ngx_http_auth_ldap_loc_conf_t *conf);
static char * ngx_http_auth_ldap(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char * ngx_http_auth_ldap_servers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
ngx_http_auth_ldap_cache_t ngx_http_auth_ldap_cache;
static ngx_command_t ngx_http_auth_ldap_commands[] = {
{
ngx_string("ldap_server"),
@ -82,6 +111,30 @@ static ngx_command_t ngx_http_auth_ldap_commands[] = {
0,
NULL
},
{
ngx_string("auth_ldap_cache_enabled"),
NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_HTTP_MAIN_CONF_OFFSET,
offsetof(ngx_http_auth_ldap_main_conf_t, cache_enabled),
NULL
},
{
ngx_string("auth_ldap_cache_expiration_time"),
NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
NGX_HTTP_MAIN_CONF_OFFSET,
offsetof(ngx_http_auth_ldap_main_conf_t, cache_expiration_time),
NULL
},
{
ngx_string("auth_ldap_cache_size"),
NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
NGX_HTTP_MAIN_CONF_OFFSET,
offsetof(ngx_http_auth_ldap_main_conf_t, cache_size),
NULL
},
{
ngx_string("auth_ldap"),
NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_TAKE1,
@ -102,29 +155,29 @@ static ngx_command_t ngx_http_auth_ldap_commands[] = {
};
static ngx_http_module_t ngx_http_auth_ldap_module_ctx = {
NULL, /* preconfiguration */
ngx_http_auth_ldap_init, /* postconfiguration */
NULL, /* preconfiguration */
ngx_http_auth_ldap_init, /* postconfiguration */
ngx_http_auth_ldap_create_main_conf, /* create main configuration */
NULL, /* init main configuration */
NULL, //ngx_http_auth_ldap_create_server_conf, /* create server configuration */
NULL, //ngx_http_auth_ldap_merge_server_conf, /* merge server configuration */
ngx_http_auth_ldap_create_loc_conf, /* create location configuration */
ngx_http_auth_ldap_merge_loc_conf /* merge location configuration */
ngx_http_auth_ldap_init_main_conf, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_auth_ldap_create_loc_conf, /* create location configuration */
ngx_http_auth_ldap_merge_loc_conf /* merge location configuration */
};
ngx_module_t ngx_http_auth_ldap_module = {
NGX_MODULE_V1,
&ngx_http_auth_ldap_module_ctx, /* module context */
ngx_http_auth_ldap_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING /**/
&ngx_http_auth_ldap_module_ctx, /* module context */
ngx_http_auth_ldap_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
ngx_http_auth_ldap_init_worker, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
@ -456,9 +509,144 @@ ngx_http_auth_ldap_create_main_conf(ngx_conf_t *cf)
return NULL;
}
conf->cache_enabled = NGX_CONF_UNSET;
conf->cache_expiration_time = NGX_CONF_UNSET_MSEC;
conf->cache_size = NGX_CONF_UNSET_SIZE;
return conf;
}
static char *
ngx_http_auth_ldap_init_main_conf(ngx_conf_t *cf, void *parent)
{
ngx_http_auth_ldap_main_conf_t *conf = parent;
if (conf->cache_enabled == NGX_CONF_UNSET) {
conf->cache_enabled = 0;
}
if (conf->cache_enabled == 0) {
return NGX_CONF_OK;
}
if (conf->cache_size == NGX_CONF_UNSET_SIZE) {
conf->cache_size = 100;
}
if (conf->cache_size < 100) {
ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "auth_ldap_cache_size cannot be smaller than 100 entries.");
return NGX_CONF_ERROR;
}
if (conf->cache_expiration_time == NGX_CONF_UNSET_MSEC) {
conf->cache_expiration_time = 10000;
}
if (conf->cache_expiration_time < 1000) {
ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "auth_ldap_cache_expiration_time cannot be smaller than 1000 ms.");
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
static ngx_int_t
ngx_http_auth_ldap_init_worker(ngx_cycle_t *cycle)
{
ngx_http_auth_ldap_main_conf_t *conf;
ngx_uint_t want, count, i;
ngx_http_auth_ldap_cache_t *cache;
static const uint16_t primes[] = {
13, 53, 101, 151, 199, 263, 317, 383, 443, 503,
577, 641, 701, 769, 839, 911, 983, 1049, 1109
};
if (ngx_process != NGX_PROCESS_SINGLE && ngx_process != NGX_PROCESS_WORKER) {
return NGX_OK;
}
conf = (ngx_http_auth_ldap_main_conf_t *) ngx_http_cycle_get_module_main_conf(cycle, ngx_http_auth_ldap_module);
if (conf == NULL || !conf->cache_enabled) {
return NGX_OK;
}
want = (conf->cache_size + 7) / 8;
for (i = 0; i < sizeof(primes)/sizeof(primes[0]); i++) {
count = primes[i];
if (count >= want) {
break;
}
}
cache = &ngx_http_auth_ldap_cache;
cache->expiration_time = conf->cache_expiration_time;
cache->num_buckets = count;
cache->elts_per_bucket = 8;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, cycle->log, 0, "http_auth_ldap: Allocating %ud bytes of LDAP cache.",
cache->num_buckets * cache->elts_per_bucket * sizeof(ngx_http_auth_ldap_cache_elt_t));
cache->buckets = (ngx_http_auth_ldap_cache_elt_t *) ngx_calloc(count * 8 * sizeof(ngx_http_auth_ldap_cache_elt_t), cycle->log);
if (cache->buckets == NULL) {
ngx_log_error(NGX_LOG_ERR, cycle->log, 0, "http_auth_ldap: Unable to allocate memory for LDAP cache.");
return NGX_ERROR;
}
return NGX_OK;
}
static ngx_int_t
ngx_http_auth_ldap_check_cache(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx,
ngx_http_auth_ldap_cache_t *cache, ngx_http_auth_ldap_server_t *server)
{
ngx_http_auth_ldap_cache_elt_t *elt;
ngx_md5_t md5ctx;
ngx_msec_t time_limit;
ngx_uint_t i;
ctx->cache_small_hash = ngx_murmur_hash2(r->headers_in.user.data, r->headers_in.user.len) ^ (uint32_t)(ngx_uint_t)server;
ngx_md5_init(&md5ctx);
ngx_md5_update(&md5ctx, r->headers_in.user.data, r->headers_in.user.len);
ngx_md5_update(&md5ctx, server, sizeof(*server));
ngx_md5_update(&md5ctx, r->headers_in.passwd.data, r->headers_in.passwd.len);
ngx_md5_final(ctx->cache_big_hash, &md5ctx);
ctx->cache_bucket = &cache->buckets[ctx->cache_small_hash % cache->num_buckets];
elt = ctx->cache_bucket;
time_limit = ngx_current_msec - cache->expiration_time;
for (i = 0; i < cache->elts_per_bucket; i++, elt++) {
if (elt->small_hash == ctx->cache_small_hash &&
elt->time > time_limit &&
memcmp(elt->big_hash, ctx->cache_big_hash, 16) == 0) {
return elt->outcome;
}
}
return -1;
}
static void
ngx_http_auth_ldap_update_cache(ngx_http_auth_ldap_ctx_t *ctx,
ngx_http_auth_ldap_cache_t *cache, ngx_flag_t outcome)
{
ngx_http_auth_ldap_cache_elt_t *elt, *oldest_elt;
ngx_uint_t i;
elt = ctx->cache_bucket;
oldest_elt = elt;
for (i = 1; i < cache->elts_per_bucket; i++, elt++) {
if (elt->time < oldest_elt->time) {
oldest_elt = elt;
}
}
oldest_elt->time = ngx_current_msec;
oldest_elt->outcome = outcome;
oldest_elt->small_hash = ctx->cache_small_hash;
ngx_memcpy(oldest_elt->big_hash, ctx->cache_big_hash, 16);
}
/**
* Create location conf
*/
@ -494,29 +682,40 @@ ngx_http_auth_ldap_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) {
* LDAP Authentication handler
*/
static ngx_int_t ngx_http_auth_ldap_handler(ngx_http_request_t *r) {
int rc;
ngx_http_auth_ldap_loc_conf_t *alcf;
ngx_http_auth_ldap_ctx_t *ctx;
int rc;
alcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_ldap_module);
if (alcf->realm.len == 0) {
return NGX_DECLINED;
}
rc = ngx_http_auth_basic_user(r);
if (rc == NGX_DECLINED) {
return ngx_http_auth_ldap_set_realm(r, &alcf->realm);
}
if (rc == NGX_ERROR) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
ctx = ngx_http_get_module_ctx(r, ngx_http_auth_ldap_module);
if (ctx == NULL) {
rc = ngx_http_auth_basic_user(r);
if (rc == NGX_DECLINED) {
return ngx_http_auth_ldap_set_realm(r, &alcf->realm);
}
if (rc == NGX_ERROR) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_auth_ldap_ctx_t));
if (ctx == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_http_set_ctx(r, ctx, ngx_http_auth_ldap_module);
}
return ngx_http_auth_ldap_authenticate(r, alcf);
return ngx_http_auth_ldap_authenticate(r, ctx, alcf);
}
/**
* Read user credentials from request, set LDAP parameters and call authentication against required servers
*/
static ngx_int_t ngx_http_auth_ldap_authenticate(ngx_http_request_t *r, ngx_http_auth_ldap_loc_conf_t *conf) {
static ngx_int_t ngx_http_auth_ldap_authenticate(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx,
ngx_http_auth_ldap_loc_conf_t *conf) {
ngx_http_auth_ldap_server_t *server;
int rc;
@ -525,7 +724,6 @@ static ngx_int_t ngx_http_auth_ldap_authenticate(ngx_http_request_t *r, ngx_http
int version = LDAP_VERSION3;
int reqcert = LDAP_OPT_X_TLS_ALLOW;
struct timeval timeOut = { 10, 0 };
ngx_flag_t pass = NGX_CONF_UNSET;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "LDAP username: %V", &r->headers_in.user);
@ -546,10 +744,28 @@ static ngx_int_t ngx_http_auth_ldap_authenticate(ngx_http_request_t *r, ngx_http
for (i = 0; i < conf->servers->nelts; i++) {
server = ((ngx_http_auth_ldap_server_t **) conf->servers->elts)[i];
pass = ngx_http_auth_ldap_authenticate_against_server(r, server, conf);
if (pass == 1) {
if (ngx_http_auth_ldap_cache.buckets != NULL) {
rc = ngx_http_auth_ldap_check_cache(r, ctx, &ngx_http_auth_ldap_cache, server);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "LDAP: Cached outcome %d", rc);
if (rc == 0) {
continue;
}
if (rc == 1) {
return NGX_OK;
}
}
rc = ngx_http_auth_ldap_authenticate_against_server(r, server, conf);
if ((rc == 0 || rc == 1) && ngx_http_auth_ldap_cache.buckets != NULL) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "LDAP: Caching outcome %d", rc);
ngx_http_auth_ldap_update_cache(ctx, &ngx_http_auth_ldap_cache, rc);
}
if (rc == 1) {
return NGX_OK;
} else if (pass == NGX_HTTP_INTERNAL_SERVER_ERROR) {
} else if (rc == NGX_HTTP_INTERNAL_SERVER_ERROR) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
}

Loading…
Cancel
Save