[PATCH] Auth basic: Cache credentials if auth_basic_user_file is static

Toshihito Kikuchi leamovret at gmail.com
Tue Oct 3 22:46:05 UTC 2023


# HG changeset patch
# User Toshihito Kikuchi <leamovret at gmail.com>
# Date 1696359541 25200
#      Tue Oct 03 11:59:01 2023 -0700
# Node ID e397ea6cfa85e85ae0865c5061397dc295fb7df1
# Parent  3db945fda515014d220151046d02f3960bcfca0a
Auth basic: Cache credentials if auth_basic_user_file is static.

In the current design, when auth_basic is on, every HTTP request triggers
file I/O (open, read, close) to the file specified in auth_basic_user_file.
Probably this is to allow auth_basic_user_file to contain variables.

If the value is just a static text, however, there is no reason to read the
same file every request in every worker process.  It unnecessarily consumes
system resources.

With this patch, if auth_basic_user_file does not have any variables, we
cache its content in the location context at configuration time and use it
in all subsequent requests.  If auth_basic_user_file contain variables, we keep
the original behavior.

diff --git a/src/http/modules/ngx_http_auth_basic_module.c b/src/http/modules/ngx_http_auth_basic_module.c
--- a/src/http/modules/ngx_http_auth_basic_module.c
+++ b/src/http/modules/ngx_http_auth_basic_module.c
@@ -15,11 +15,21 @@
 
 
 typedef struct {
+    ngx_chain_t                cache;
     ngx_http_complex_value_t  *realm;
     ngx_http_complex_value_t  *user_file;
 } ngx_http_auth_basic_loc_conf_t;
 
 
+typedef struct {
+    off_t                            offset;
+    ngx_file_t                       file;
+    ngx_chain_t                     *chain;
+    ngx_http_request_t              *r;
+    ngx_http_auth_basic_loc_conf_t  *alcf;
+} ngx_http_auth_basic_file_ctx_t;
+
+
 static ngx_int_t ngx_http_auth_basic_handler(ngx_http_request_t *r);
 static ngx_int_t ngx_http_auth_basic_crypt_handler(ngx_http_request_t *r,
     ngx_str_t *passwd, ngx_str_t *realm);
@@ -31,6 +41,14 @@ static char *ngx_http_auth_basic_merge_l
 static ngx_int_t ngx_http_auth_basic_init(ngx_conf_t *cf);
 static char *ngx_http_auth_basic_user_file(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
+static ngx_int_t ngx_http_auth_basic_read_file(ngx_pool_t *pool,
+    ngx_str_t *filename, ngx_log_t *log, ngx_chain_t *chain_out);
+static ngx_int_t ngx_http_auth_basic_init_file_ctx(
+    ngx_http_auth_basic_file_ctx_t *ctx);
+static ssize_t ngx_http_auth_basic_read_file_ctx(
+    ngx_http_auth_basic_file_ctx_t *ctx, u_char *buf_out, size_t size);
+static void ngx_http_auth_basic_cleanup_file_ctx(
+    ngx_http_auth_basic_file_ctx_t *ctx);
 
 
 static ngx_command_t  ngx_http_auth_basic_commands[] = {
@@ -89,14 +107,11 @@ ngx_module_t  ngx_http_auth_basic_module
 static ngx_int_t
 ngx_http_auth_basic_handler(ngx_http_request_t *r)
 {
-    off_t                            offset;
     ssize_t                          n;
-    ngx_fd_t                         fd;
     ngx_int_t                        rc;
-    ngx_err_t                        err;
-    ngx_str_t                        pwd, realm, user_file;
-    ngx_uint_t                       i, level, login, left, passwd;
-    ngx_file_t                       file;
+    ngx_str_t                        pwd, realm;
+    ngx_uint_t                       i, login, left, passwd;
+    ngx_http_auth_basic_file_ctx_t   file_ctx;
     ngx_http_auth_basic_loc_conf_t  *alcf;
     u_char                           buf[NGX_HTTP_AUTH_BUF_SIZE];
     enum {
@@ -133,47 +148,23 @@ ngx_http_auth_basic_handler(ngx_http_req
         return NGX_HTTP_INTERNAL_SERVER_ERROR;
     }
 
-    if (ngx_http_complex_value(r, alcf->user_file, &user_file) != NGX_OK) {
-        return NGX_ERROR;
-    }
-
-    fd = ngx_open_file(user_file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
-
-    if (fd == NGX_INVALID_FILE) {
-        err = ngx_errno;
-
-        if (err == NGX_ENOENT) {
-            level = NGX_LOG_ERR;
-            rc = NGX_HTTP_FORBIDDEN;
-
-        } else {
-            level = NGX_LOG_CRIT;
-            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
-        }
-
-        ngx_log_error(level, r->connection->log, err,
-                      ngx_open_file_n " \"%s\" failed", user_file.data);
-
+    file_ctx.alcf = alcf;
+    file_ctx.r = r;
+    rc = ngx_http_auth_basic_init_file_ctx(&file_ctx);
+    if (rc != NGX_OK) {
         return rc;
     }
 
-    ngx_memzero(&file, sizeof(ngx_file_t));
-
-    file.fd = fd;
-    file.name = user_file;
-    file.log = r->connection->log;
-
     state = sw_login;
     passwd = 0;
     login = 0;
     left = 0;
-    offset = 0;
 
     for ( ;; ) {
         i = left;
 
-        n = ngx_read_file(&file, buf + left, NGX_HTTP_AUTH_BUF_SIZE - left,
-                          offset);
+        n = ngx_http_auth_basic_read_file_ctx(&file_ctx, buf + left,
+                                              NGX_HTTP_AUTH_BUF_SIZE - left);
 
         if (n == NGX_ERROR) {
             rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
@@ -245,8 +236,6 @@ ngx_http_auth_basic_handler(ngx_http_req
         } else {
             left = 0;
         }
-
-        offset += n;
     }
 
     if (state == sw_passwd) {
@@ -264,16 +253,13 @@ ngx_http_auth_basic_handler(ngx_http_req
 
     ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                   "user \"%V\" was not found in \"%s\"",
-                  &r->headers_in.user, user_file.data);
+                  &r->headers_in.user, &file_ctx.file.name.data);
 
     rc = ngx_http_auth_basic_set_realm(r, &realm);
 
 cleanup:
 
-    if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
-        ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
-                      ngx_close_file_n " \"%s\" failed", user_file.data);
-    }
+    ngx_http_auth_basic_cleanup_file_ctx(&file_ctx);
 
     ngx_explicit_memzero(buf, NGX_HTTP_AUTH_BUF_SIZE);
 
@@ -374,6 +360,19 @@ ngx_http_auth_basic_merge_loc_conf(ngx_c
     ngx_conf_merge_ptr_value(conf->realm, prev->realm, NULL);
     ngx_conf_merge_ptr_value(conf->user_file, prev->user_file, NULL);
 
+    if (conf->user_file != NULL && conf->user_file->lengths == NULL) {
+
+        /*
+         * If the given expression is a static text, we read the file at
+         * configuration time.
+         */
+
+        if (ngx_http_auth_basic_read_file(cf->pool, &conf->user_file->value,
+                                          cf->log, &conf->cache) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
     return NGX_CONF_OK;
 }
 
@@ -430,3 +429,207 @@ ngx_http_auth_basic_user_file(ngx_conf_t
 
     return NGX_CONF_OK;
 }
+
+
+static ngx_int_t
+ngx_http_auth_basic_read_file(ngx_pool_t *pool, ngx_str_t *filename,
+    ngx_log_t *log, ngx_chain_t *chain_out)
+{
+    off_t         offset;
+    ssize_t       n;
+    ngx_fd_t      fd;
+    ngx_buf_t    *p;
+    ngx_int_t     rc;
+    ngx_file_t    file;
+    ngx_chain_t  *cl;
+    u_char        buf[NGX_HTTP_AUTH_BUF_SIZE];
+
+    if (chain_out == NULL
+        || chain_out->buf != NULL
+        || chain_out->next != NULL) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "ngx_http_auth_basic_read_file() accepts only an empty"
+                      " chain.");
+        return NGX_ERROR;
+    }
+
+    fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
+    if (fd == NGX_INVALID_FILE) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      ngx_open_file_n " \"%V\" failed", filename);
+        return NGX_ENOENT;
+    }
+
+    ngx_memzero(&file, sizeof(ngx_file_t));
+    file.fd = fd;
+    file.name = *filename;
+    file.log = log;
+
+    rc = NGX_OK;
+
+    for (offset = 0; /* void */ ; offset += n) {
+        n = ngx_read_file(&file, buf, NGX_HTTP_AUTH_BUF_SIZE, offset);
+        if (n == 0) {
+            break;
+
+        } else if (n == NGX_ERROR) {
+            rc = NGX_ERROR;
+            break;
+        }
+
+        p = ngx_create_temp_buf(pool, n);
+        if (p == NULL) {
+            rc = NGX_ENOMEM;
+            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                          "Cannot allocate a buffer");
+            break;
+        }
+
+        ngx_memcpy(p->start, buf, n);
+
+        if (chain_out->buf == NULL) {
+
+            /* First chain is provided by the caller.  No allocation needed. */
+
+            chain_out->buf = p;
+
+        } else {
+            cl = ngx_alloc_chain_link(pool);
+            if (cl == NULL) {
+                rc = NGX_ENOMEM;
+                ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                              "Cannot allocate a chain");
+                break;
+            }
+
+            cl->buf = p;
+            chain_out->next = cl;
+            chain_out = cl;
+        }
+    }
+
+    if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      ngx_close_file_n " \"%V\" failed", filename);
+    }
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_auth_basic_init_file_ctx(ngx_http_auth_basic_file_ctx_t *ctx)
+{
+    ngx_fd_t                         fd;
+    ngx_err_t                        err;
+    ngx_int_t                        rc;
+    ngx_str_t                        user_file;
+    ngx_uint_t                       level;
+    ngx_http_request_t              *r;
+    ngx_http_auth_basic_loc_conf_t  *alcf;
+
+    r = ctx->r;
+    alcf = ctx->alcf;
+
+    ngx_memzero(&ctx->file, sizeof(ngx_file_t));
+    ctx->chain = NULL;
+    ctx->offset = 0;
+
+    if (alcf->user_file->lengths == NULL) {
+        ctx->chain = &alcf->cache;
+        ctx->file.name = alcf->user_file->value;
+        return NGX_OK;
+    }
+
+    if (ngx_http_complex_value(r, alcf->user_file, &user_file) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    fd = ngx_open_file(user_file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
+
+    if (fd == NGX_INVALID_FILE) {
+        err = ngx_errno;
+
+        if (err == NGX_ENOENT) {
+            level = NGX_LOG_ERR;
+            rc = NGX_HTTP_FORBIDDEN;
+
+        } else {
+            level = NGX_LOG_CRIT;
+            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        ngx_log_error(level, r->connection->log, err,
+                      ngx_open_file_n " \"%s\" failed", user_file.data);
+
+        return rc;
+    }
+
+    ctx->file.fd = fd;
+    ctx->file.name = user_file;
+    ctx->file.log = r->connection->log;
+
+    return NGX_OK;
+}
+
+
+static ssize_t
+ngx_http_auth_basic_read_file_ctx(ngx_http_auth_basic_file_ctx_t *ctx,
+    u_char *buf_out, size_t size)
+{
+    off_t         offset;
+    size_t        remaining;
+    u_char       *p;
+    ssize_t       n;
+    ngx_buf_t    *buf;
+    ngx_chain_t  *cl;
+
+    if (!ctx->alcf->user_file->lengths) {
+        offset = ctx->offset;
+        p = buf_out;
+        n = 0;
+
+        for (cl = ctx->chain; cl; cl = cl->next) {
+            buf = cl->buf;
+            remaining = buf->end - buf->start - offset;
+            if (size <= remaining) {
+                ngx_memcpy(p, buf->start, size);
+                n += size;
+                offset += size;
+                break;
+            }
+
+            ngx_memcpy(p, buf->start, remaining);
+            n += remaining;
+            p += remaining;
+            size -= remaining;
+            offset = 0;
+        }
+
+        ctx->chain = cl;
+        ctx->offset = offset;
+        return n;
+    }
+
+    n = ngx_read_file(&ctx->file, buf_out, size, ctx->offset);
+    if (n == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    ctx->offset += n;
+    return n;
+}
+
+
+static void
+ngx_http_auth_basic_cleanup_file_ctx(ngx_http_auth_basic_file_ctx_t *ctx)
+{
+    if (ctx->alcf->user_file->lengths == NULL) {
+        return;
+    }
+
+    if (ngx_close_file(ctx->file.fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, ctx->r->connection->log, ngx_errno,
+                      ngx_close_file_n " \"%s\" failed", ctx->file.name.data);
+    }
+}


More information about the nginx-devel mailing list