From noreply at nginx.com Mon Jan 6 23:53:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 6 Jan 2025 23:53:02 +0000 (UTC) Subject: [njs] Version bump. Message-ID: <20250106235302.A6C0847810@pubserv1.nginx> details: https://github.com/nginx/njs/commit/4fb1c0ca6c950dc0460eaeec1ba3e96a53070878 branches: master commit: 4fb1c0ca6c950dc0460eaeec1ba3e96a53070878 user: Dmitry Volyntsev date: Mon, 6 Jan 2025 09:09:43 -0800 description: Version bump. --- src/njs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/njs.h b/src/njs.h index 06eb770c..42a6f405 100644 --- a/src/njs.h +++ b/src/njs.h @@ -11,8 +11,8 @@ #include -#define NJS_VERSION "0.8.8" -#define NJS_VERSION_NUMBER 0x000808 +#define NJS_VERSION "0.8.9" +#define NJS_VERSION_NUMBER 0x000809 #include From noreply at nginx.com Mon Jan 6 23:53:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 6 Jan 2025 23:53:02 +0000 (UTC) Subject: [njs] Modules: removed extra VM creation per server. Message-ID: <20250106235302.AC1FD48EF4@pubserv1.nginx> details: https://github.com/nginx/njs/commit/855aa4c9fac01bd9fbdb1602b523edc00117ff09 branches: master commit: 855aa4c9fac01bd9fbdb1602b523edc00117ff09 user: Dmitry Volyntsev date: Fri, 3 Jan 2025 22:25:15 -0800 description: Modules: removed extra VM creation per server. Previously, when js_import was declared in http or stream blocks, an extra copy of the VM instance was created for each server block. This was not needed and consumed a lot of memory for configurations with many server blocks. This issue was introduced in 9b674412 (0.8.6) and was partially fixed for location blocks only in 685b64f0 (0.8.7). --- nginx/ngx_js.c | 17 ++++++++ nginx/t/js_import2.t | 12 +++++- nginx/t/js_merge_location_blocks.t | 83 ++++++++++++++++++++++++++++++++++++++ nginx/t/js_merge_server_blocks.t | 78 +++++++++++++++++++++++++++++++++++ nginx/t/stream_js.t | 6 ++- 5 files changed, 194 insertions(+), 2 deletions(-) diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index 12b577a2..5c2a44cb 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -3343,6 +3343,16 @@ ngx_js_merge_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, ngx_array_t *imports, *preload_objects, *paths; ngx_js_named_path_t *import, *pi, *pij, *preload; + if (prev->imports != NGX_CONF_UNSET_PTR && prev->engine == NULL) { + /* + * special handling to preserve conf->engine + * in the "http" or "stream" section to inherit it to all servers + */ + if (init_vm(cf, (ngx_js_loc_conf_t *) prev) != NGX_OK) { + return NGX_ERROR; + } + } + if (conf->imports == NGX_CONF_UNSET_PTR && conf->type == prev->type && conf->paths == NGX_CONF_UNSET_PTR @@ -3851,6 +3861,9 @@ ngx_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, return NGX_ERROR; } + ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "js vm init %s: %p", + conf->engine->name, conf->engine); + cln = ngx_pool_cleanup_add(cf->pool, 0); if (cln == NULL) { return NGX_ERROR; @@ -4039,6 +4052,10 @@ ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child, ngx_js_loc_conf_t *conf = child; ngx_conf_merge_uint_value(conf->type, prev->type, NGX_ENGINE_NJS); + if (prev->type == NGX_CONF_UNSET_UINT) { + prev->type = NGX_ENGINE_NJS; + } + ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000); ngx_conf_merge_size_value(conf->reuse, prev->reuse, 128); ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 16384); diff --git a/nginx/t/js_import2.t b/nginx/t/js_import2.t index cd29d2dc..7fdc624d 100644 --- a/nginx/t/js_import2.t +++ b/nginx/t/js_import2.t @@ -41,6 +41,7 @@ http { js_set $test foo.bar.p; + # context 1 js_import foo from main.js; location /njs { @@ -52,11 +53,13 @@ http { } location /test_lib { + # context 2 js_import lib.js; js_content lib.test; } location /test_fun { + # context 3 js_import fun.js; js_content fun; } @@ -75,6 +78,7 @@ http { server_name localhost; location /test_fun { + # context 4 js_import fun.js; js_content fun; } @@ -114,7 +118,7 @@ $t->write_file('main.js', <try_run('no njs available')->plan(5); +$t->try_run('no njs available')->plan(6); ############################################################################### @@ -124,4 +128,10 @@ like(http_get('/test_fun'), qr/FUN-TEST/s, 'fun'); like(http_get('/proxy/test_fun'), qr/FUN-TEST/s, 'proxy fun'); like(http_get('/test_var'), qr/P-TEST/s, 'foo.bar.p'); +$t->stop(); + +my $content = $t->read_file('error.log'); +my $count = () = $content =~ m/js vm init/g; +ok($count == 4, 'uniq js vm contexts'); + ############################################################################### diff --git a/nginx/t/js_merge_location_blocks.t b/nginx/t/js_merge_location_blocks.t new file mode 100644 index 00000000..328d5338 --- /dev/null +++ b/nginx/t/js_merge_location_blocks.t @@ -0,0 +1,83 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (c) Nginx, Inc. + +# Tests for http njs module, check for proper location blocks merging. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import main.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /a { + js_content main.version; + } + + location /b { + js_content main.version; + } + + location /c { + js_content main.version; + } + + location /d { + js_content main.version; + } + } +} + +EOF + +$t->write_file('main.js', <try_run('no njs available')->plan(1); + +############################################################################### + +$t->stop(); + +my $content = $t->read_file('error.log'); +my $count = () = $content =~ m/ js vm init/g; +ok($count == 1, 'http js block imported once'); + +############################################################################### diff --git a/nginx/t/js_merge_server_blocks.t b/nginx/t/js_merge_server_blocks.t new file mode 100644 index 00000000..c3f261af --- /dev/null +++ b/nginx/t/js_merge_server_blocks.t @@ -0,0 +1,78 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (c) Nginx, Inc. + +# Tests for http njs module, check for proper server blocks merging. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import main.js; + + server { + listen 127.0.0.1:8080; + } + + server { + listen 127.0.0.1:8081; + } + + server { + listen 127.0.0.1:8082; + } + + server { + listen 127.0.0.1:8083; + } +} + +EOF + +$t->write_file('main.js', <try_run('no njs available')->plan(1); + +############################################################################### + +$t->stop(); + +my $content = $t->read_file('error.log'); +my $count = () = $content =~ m/ js vm init/g; +ok($count == 1, 'http js block imported once'); + +############################################################################### diff --git a/nginx/t/stream_js.t b/nginx/t/stream_js.t index 52ab688e..0834b68a 100644 --- a/nginx/t/stream_js.t +++ b/nginx/t/stream_js.t @@ -394,7 +394,7 @@ $t->write_file('test.js', <run_daemon(\&stream_daemon, port(8090)); -$t->try_run('no stream njs available')->plan(24); +$t->try_run('no stream njs available')->plan(25); $t->waitforsocket('127.0.0.1:' . port(8090)); ############################################################################### @@ -450,6 +450,10 @@ like($t->read_file('status.log'), qr/$p[0]:200/, 'status undecided'); like($t->read_file('status.log'), qr/$p[1]:200/, 'status allow'); like($t->read_file('status.log'), qr/$p[2]:403/, 'status deny'); +my $content = $t->read_file('error.log'); +my $count = () = $content =~ m/ js vm init/g; +ok($count == 2, 'http and stream js blocks imported once each'); + ############################################################################### sub has_version { From noreply at nginx.com Thu Jan 9 13:09:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 9 Jan 2025 13:09:02 +0000 (UTC) Subject: [nginx] Year 2025. Message-ID: <20250109130902.8EF1347816@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/febe6e728ff83cfc5d5bcc0c74b4d8d63dc296b0 branches: master commit: febe6e728ff83cfc5d5bcc0c74b4d8d63dc296b0 user: Roman Arutyunyan date: Thu, 9 Jan 2025 17:00:14 +0400 description: Year 2025. --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index a7ec58a75..4efd90b51 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ Copyright (C) 2002-2021 Igor Sysoev -Copyright (C) 2011-2024 Nginx, Inc. +Copyright (C) 2011-2025 Nginx, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without From noreply at nginx.com Thu Jan 9 13:20:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 9 Jan 2025 13:20:02 +0000 (UTC) Subject: [nginx] Gzip: compatibility with recent zlib-ng 2.2.x versions. Message-ID: <20250109132002.8F90947818@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/57d54fd922e7ecbebb78598d13adc9df1a4b69c0 branches: master commit: 57d54fd922e7ecbebb78598d13adc9df1a4b69c0 user: Sergey Kandaurov date: Mon, 23 Dec 2024 17:57:45 +0400 description: Gzip: compatibility with recent zlib-ng 2.2.x versions. It now uses 5/4 times more memory for the pending buffer. Further, a single allocation is now used, which takes additional 56 bytes for deflate_allocs in 64-bit mode aligned to 16, to store sub-allocation pointers, and the total allocation size now padded up to 128 bytes, which takes theoretically 200 additional bytes in total. This fits though into "4 * (64 + sizeof(void*))" additional space for ZALLOC used in zlib-ng 2.1.x versions. The comment was updated to reflect this. --- src/http/modules/ngx_http_gzip_filter_module.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/http/modules/ngx_http_gzip_filter_module.c b/src/http/modules/ngx_http_gzip_filter_module.c index b55527845..7113df695 100644 --- a/src/http/modules/ngx_http_gzip_filter_module.c +++ b/src/http/modules/ngx_http_gzip_filter_module.c @@ -516,8 +516,10 @@ ngx_http_gzip_filter_memory(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) /* * Another zlib variant, https://github.com/zlib-ng/zlib-ng. * It used to force window bits to 13 for fast compression level, - * uses (64 + sizeof(void*)) additional space on all allocations - * for alignment, 16-byte padding in one of window-sized buffers, + * used (64 + sizeof(void*)) additional space on all allocations + * for alignment and 16-byte padding in one of window-sized buffers, + * uses a single allocation with up to 200 bytes for alignment and + * internal pointers, 5/4 times more memory for the pending buffer, * and 128K hash. */ @@ -526,7 +528,7 @@ ngx_http_gzip_filter_memory(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) } ctx->allocated = 8192 + 16 + (1 << (wbits + 2)) - + 131072 + (1 << (memlevel + 8)) + + 131072 + (5 << (memlevel + 6)) + 4 * (64 + sizeof(void*)); ctx->zlib_ng = 1; } From noreply at nginx.com Mon Jan 13 23:31:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 13 Jan 2025 23:31:02 +0000 (UTC) Subject: [njs] 2025 year. Message-ID: <20250113233102.7428D4873D@pubserv1.nginx> details: https://github.com/nginx/njs/commit/17676e5c9a9f5bdd63509632c31a9019686b171e branches: master commit: 17676e5c9a9f5bdd63509632c31a9019686b171e user: Dmitry Volyntsev date: Mon, 13 Jan 2025 14:58:28 -0800 description: 2025 year. --- LICENSE | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index b510378f..187d237a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,10 @@ /* - * Copyright (C) 2015-2024 NGINX, Inc. + * Copyright (C) 2015-2019 NGINX, Inc. + * Copyright (C) 2019-2025 F5, Inc. * Copyright (C) 2015-2021 Igor Sysoev - * Copyright (C) 2017-2024 Dmitry Volyntsev + * Copyright (C) 2017-2025 Dmitry Volyntsev * Copyright (C) 2019-2022 Alexander Borisov - * Copyright (C) 2022-2024 Vadim Zhestikov + * Copyright (C) 2022-2025 Vadim Zhestikov * All rights reserved. * * Redistribution and use in source and binary forms, with or without From noreply at nginx.com Mon Jan 13 23:31:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 13 Jan 2025 23:31:02 +0000 (UTC) Subject: [njs] Version 0.8.9. Message-ID: <20250113233102.77B7548EF2@pubserv1.nginx> details: https://github.com/nginx/njs/commit/b87ad67adb2c557bd96e52a3221748a7ba028858 branches: master commit: b87ad67adb2c557bd96e52a3221748a7ba028858 user: Dmitry Volyntsev date: Mon, 13 Jan 2025 14:56:10 -0800 description: Version 0.8.9. --- CHANGES | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGES b/CHANGES index cc7ebef4..c67eaf8b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,20 @@ +Changes with njs 0.8.9 14 Jan 2025 + + nginx modules: + + *) Bugfix: removed extra VM creation per server. + Previously, when js_import was declared in http or stream blocks, + an extra copy of the VM instance was created for each server + block. This was not needed and consumed a lot of memory for + configurations with many server blocks. + + This issue was introduced in 9b674412 (0.8.6) and was partially + fixed for location blocks only in 685b64f0 (0.8.7). + + Core: + + *) Feature: added fs module for QuickJS engine. + Changes with njs 0.8.8 10 Dec 2024 nginx modules: From noreply at nginx.com Mon Jan 13 23:33:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 13 Jan 2025 23:33:02 +0000 (UTC) Subject: [njs] Lightweight tag created: 0.8.9 Message-ID: <20250113233302.34F9C48EF2@pubserv1.nginx> details: https://github.com/nginx/njs/releases/tag/0.8.9 branches: commit: b87ad67adb2c557bd96e52a3221748a7ba028858 user: Dmitry Volyntsev date: Mon Jan 13 14:56:10 2025 -0800 description: Version 0.8.9. From noreply at nginx.com Thu Jan 16 17:10:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 16 Jan 2025 17:10:02 +0000 (UTC) Subject: [nginx] Slice filter: log the expected range in case of range error. Message-ID: <20250116171002.CA63148F15@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/47f862ffad6a7068100d50887c495f80973ca47b branches: master commit: 47f862ffad6a7068100d50887c495f80973ca47b user: Daniel Vasquez Lopez date: Thu, 21 Nov 2024 14:27:07 -0800 description: Slice filter: log the expected range in case of range error. --- src/http/modules/ngx_http_slice_filter_module.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/http/modules/ngx_http_slice_filter_module.c b/src/http/modules/ngx_http_slice_filter_module.c index 186380a2f..3b0bef629 100644 --- a/src/http/modules/ngx_http_slice_filter_module.c +++ b/src/http/modules/ngx_http_slice_filter_module.c @@ -165,8 +165,8 @@ ngx_http_slice_header_filter(ngx_http_request_t *r) if (cr.start != ctx->start || cr.end != end) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "unexpected range in slice response: %O-%O", - cr.start, cr.end); + "unexpected range in slice response: %O-%O, " + "expected: %O-%O", cr.start, cr.end, ctx->start, end); return NGX_ERROR; } From noreply at nginx.com Fri Jan 17 00:38:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 17 Jan 2025 00:38:02 +0000 (UTC) Subject: [nginx] SSL: encrypted certificate keys are exempt from object cache. Message-ID: <20250117003802.4490948F15@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/7677d5646aeb761b8b9da5af3eb10c008aae3f90 branches: master commit: 7677d5646aeb761b8b9da5af3eb10c008aae3f90 user: Sergey Kandaurov date: Wed, 18 Dec 2024 20:09:58 +0400 description: SSL: encrypted certificate keys are exempt from object cache. SSL object cache, as previously introduced in 1.27.2, did not take into account encrypted certificate keys that might be unexpectedly fetched from the cache regardless of the matching passphrase. To avoid this, caching of encrypted certificate keys is now disabled based on the passphrase callback invocation. A notable exception is encrypted certificate keys configured without ssl_password_file. They are loaded once resulting in the passphrase prompt on startup and reused in other contexts as applicable. --- src/event/ngx_event_openssl_cache.c | 53 ++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c index 9d1962759..c79f77456 100644 --- a/src/event/ngx_event_openssl_cache.c +++ b/src/event/ngx_event_openssl_cache.c @@ -13,6 +13,8 @@ #define NGX_SSL_CACHE_DATA 1 #define NGX_SSL_CACHE_ENGINE 2 +#define NGX_SSL_CACHE_DISABLED (ngx_array_t *) (uintptr_t) -1 + #define ngx_ssl_cache_get_conf(cycle) \ (ngx_ssl_cache_t *) ngx_get_conf(cycle->conf_ctx, ngx_openssl_cache_module) @@ -61,6 +63,12 @@ typedef struct { } ngx_ssl_cache_t; +typedef struct { + ngx_str_t *pwd; + unsigned encrypted:1; +} ngx_ssl_cache_pwd_t; + + static ngx_int_t ngx_ssl_cache_init_key(ngx_pool_t *pool, ngx_uint_t index, ngx_str_t *path, ngx_ssl_cache_key_t *id); static ngx_ssl_cache_node_t *ngx_ssl_cache_lookup(ngx_ssl_cache_t *cache, @@ -228,9 +236,10 @@ ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_uint_t index, char **err, } if (value == NULL) { - value = type->create(&id, err, data); - if (value == NULL) { - return NULL; + value = type->create(&id, err, &data); + + if (value == NULL || data == NGX_SSL_CACHE_DISABLED) { + return value; } } @@ -269,7 +278,7 @@ ngx_ssl_cache_connection_fetch(ngx_pool_t *pool, ngx_uint_t index, char **err, return NULL; } - return ngx_ssl_cache_types[index].create(&id, err, data); + return ngx_ssl_cache_types[index].create(&id, err, &data); } @@ -472,13 +481,13 @@ ngx_ssl_cache_cert_ref(char **err, void *data) static void * ngx_ssl_cache_pkey_create(ngx_ssl_cache_key_t *id, char **err, void *data) { - ngx_array_t *passwords = data; + ngx_array_t **passwords = data; - BIO *bio; - EVP_PKEY *pkey; - ngx_str_t *pwd; - ngx_uint_t tries; - pem_password_cb *cb; + BIO *bio; + EVP_PKEY *pkey; + ngx_uint_t tries; + pem_password_cb *cb; + ngx_ssl_cache_pwd_t cb_data, *pwd; if (id->type == NGX_SSL_CACHE_ENGINE) { @@ -531,12 +540,16 @@ ngx_ssl_cache_pkey_create(ngx_ssl_cache_key_t *id, char **err, void *data) return NULL; } - if (passwords) { - tries = passwords->nelts; - pwd = passwords->elts; + cb_data.encrypted = 0; + + if (*passwords) { + cb_data.pwd = (*passwords)->elts; + tries = (*passwords)->nelts; + pwd = &cb_data; cb = ngx_ssl_cache_pkey_password_callback; } else { + cb_data.pwd = NULL; tries = 1; pwd = NULL; cb = NULL; @@ -552,7 +565,7 @@ ngx_ssl_cache_pkey_create(ngx_ssl_cache_key_t *id, char **err, void *data) if (tries-- > 1) { ERR_clear_error(); (void) BIO_reset(bio); - pwd++; + cb_data.pwd++; continue; } @@ -561,6 +574,10 @@ ngx_ssl_cache_pkey_create(ngx_ssl_cache_key_t *id, char **err, void *data) return NULL; } + if (cb_data.encrypted) { + *passwords = NGX_SSL_CACHE_DISABLED; + } + BIO_free(bio); return pkey; @@ -571,7 +588,9 @@ static int ngx_ssl_cache_pkey_password_callback(char *buf, int size, int rwflag, void *userdata) { - ngx_str_t *pwd = userdata; + ngx_ssl_cache_pwd_t *data = userdata; + + ngx_str_t *pwd; if (rwflag) { ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, @@ -580,6 +599,10 @@ ngx_ssl_cache_pkey_password_callback(char *buf, int size, int rwflag, return 0; } + data->encrypted = 1; + + pwd = data->pwd; + if (pwd == NULL) { return 0; } From noreply at nginx.com Fri Jan 17 00:38:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 17 Jan 2025 00:38:02 +0000 (UTC) Subject: [nginx] SSL: cache revalidation of file based dynamic certificates. Message-ID: <20250117003802.541A048F17@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/4b96ad14f3607ab39b160715aeba721097ac4da4 branches: master commit: 4b96ad14f3607ab39b160715aeba721097ac4da4 user: Sergey Kandaurov date: Mon, 13 Jan 2025 21:40:04 +0400 description: SSL: cache revalidation of file based dynamic certificates. Revalidation is based on file modification time and uniq file index, and happens after the cache object validity time is expired. --- src/event/ngx_event_openssl_cache.c | 44 +++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c index 7589e6c90..eb03e16b2 100644 --- a/src/event/ngx_event_openssl_cache.c +++ b/src/event/ngx_event_openssl_cache.c @@ -289,6 +289,7 @@ ngx_ssl_cache_connection_fetch(ngx_ssl_cache_t *cache, ngx_pool_t *pool, void *value; time_t now; uint32_t hash; + ngx_file_info_t fi; ngx_ssl_cache_key_t id; ngx_ssl_cache_type_t *type; ngx_ssl_cache_node_t *cn; @@ -318,7 +319,33 @@ ngx_ssl_cache_connection_fetch(ngx_ssl_cache_t *cache, ngx_pool_t *pool, goto found; } - if (now - cn->created > cache->valid) { + if (now - cn->created <= cache->valid) { + goto found; + } + + switch (id.type) { + + case NGX_SSL_CACHE_PATH: + + if (ngx_file_info(id.data, &fi) != NGX_FILE_ERROR) { + + if (ngx_file_uniq(&fi) == cn->uniq + && ngx_file_mtime(&fi) == cn->mtime) + { + break; + } + + cn->mtime = ngx_file_mtime(&fi); + cn->uniq = ngx_file_uniq(&fi); + + } else { + cn->mtime = 0; + cn->uniq = 0; + } + + /* fall through */ + + default: ngx_log_debug1(NGX_LOG_DEBUG_CORE, pool->log, 0, "update cached ssl object: %s", cn->id.data); @@ -337,9 +364,10 @@ ngx_ssl_cache_connection_fetch(ngx_ssl_cache_t *cache, ngx_pool_t *pool, } cn->value = value; - cn->created = now; } + cn->created = now; + goto found; } @@ -365,6 +393,18 @@ ngx_ssl_cache_connection_fetch(ngx_ssl_cache_t *cache, ngx_pool_t *pool, ngx_cpystrn(cn->id.data, id.data, id.len + 1); + if (id.type == NGX_SSL_CACHE_PATH) { + + if (ngx_file_info(id.data, &fi) != NGX_FILE_ERROR) { + cn->mtime = ngx_file_mtime(&fi); + cn->uniq = ngx_file_uniq(&fi); + + } else { + cn->mtime = 0; + cn->uniq = 0; + } + } + ngx_ssl_cache_expire(cache, 1, pool->log); if (cache->current >= cache->max) { From noreply at nginx.com Fri Jan 17 00:38:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 17 Jan 2025 00:38:02 +0000 (UTC) Subject: [nginx] SSL: object cache inheritance from the old configuration cycle. Message-ID: <20250117003802.418B547857@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/8311e14ae614529aabe9e72e87051d191b723fb4 branches: master commit: 8311e14ae614529aabe9e72e87051d191b723fb4 user: Sergey Kandaurov date: Wed, 18 Dec 2024 20:03:35 +0400 description: SSL: object cache inheritance from the old configuration cycle. Memory based objects are always inherited, engine based objects are never inherited to adhere the volatile nature of engines, file based objects are inherited subject to modification time and file index. The previous behaviour to bypass cache from the old configuration cycle is preserved with a new directive "ssl_object_cache_inheritable off;". --- src/event/ngx_event_openssl_cache.c | 107 +++++++++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c index 8829e2879..9d1962759 100644 --- a/src/event/ngx_event_openssl_cache.c +++ b/src/event/ngx_event_openssl_cache.c @@ -14,6 +14,14 @@ #define NGX_SSL_CACHE_ENGINE 2 +#define ngx_ssl_cache_get_conf(cycle) \ + (ngx_ssl_cache_t *) ngx_get_conf(cycle->conf_ctx, ngx_openssl_cache_module) + +#define ngx_ssl_cache_get_old_conf(cycle) \ + cycle->old_cycle->conf_ctx ? ngx_ssl_cache_get_conf(cycle->old_cycle) \ + : NULL + + typedef struct { unsigned type:2; unsigned len:30; @@ -39,12 +47,17 @@ typedef struct { ngx_ssl_cache_key_t id; ngx_ssl_cache_type_t *type; void *value; + + time_t mtime; + ngx_file_uniq_t uniq; } ngx_ssl_cache_node_t; typedef struct { ngx_rbtree_t rbtree; ngx_rbtree_node_t sentinel; + + ngx_flag_t inheritable; } ngx_ssl_cache_t; @@ -76,22 +89,36 @@ static void *ngx_ssl_cache_ca_create(ngx_ssl_cache_key_t *id, char **err, static BIO *ngx_ssl_cache_create_bio(ngx_ssl_cache_key_t *id, char **err); static void *ngx_openssl_cache_create_conf(ngx_cycle_t *cycle); +static char *ngx_openssl_cache_init_conf(ngx_cycle_t *cycle, void *conf); static void ngx_ssl_cache_cleanup(void *data); static void ngx_ssl_cache_node_insert(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); +static ngx_command_t ngx_openssl_cache_commands[] = { + + { ngx_string("ssl_object_cache_inheritable"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + 0, + offsetof(ngx_ssl_cache_t, inheritable), + NULL }, + + ngx_null_command +}; + + static ngx_core_module_t ngx_openssl_cache_module_ctx = { ngx_string("openssl_cache"), ngx_openssl_cache_create_conf, - NULL + ngx_openssl_cache_init_conf }; ngx_module_t ngx_openssl_cache_module = { NGX_MODULE_V1, &ngx_openssl_cache_module_ctx, /* module context */ - NULL, /* module directives */ + ngx_openssl_cache_commands, /* module directives */ NGX_CORE_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ @@ -132,8 +159,13 @@ void * ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_uint_t index, char **err, ngx_str_t *path, void *data) { + void *value; + time_t mtime; uint32_t hash; - ngx_ssl_cache_t *cache; + ngx_int_t rc; + ngx_file_uniq_t uniq; + ngx_file_info_t fi; + ngx_ssl_cache_t *cache, *old_cache; ngx_ssl_cache_key_t id; ngx_ssl_cache_type_t *type; ngx_ssl_cache_node_t *cn; @@ -151,12 +183,60 @@ ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_uint_t index, char **err, hash = ngx_murmur_hash2(id.data, id.len); cn = ngx_ssl_cache_lookup(cache, type, &id, hash); + if (cn != NULL) { return type->ref(err, cn->value); } + value = NULL; + + if (id.type == NGX_SSL_CACHE_PATH + && (rc = ngx_file_info(id.data, &fi)) != NGX_FILE_ERROR) + { + mtime = ngx_file_mtime(&fi); + uniq = ngx_file_uniq(&fi); + + } else { + rc = NGX_FILE_ERROR; + mtime = 0; + uniq = 0; + } + + /* try to use a reference from the old cycle */ + + old_cache = ngx_ssl_cache_get_old_conf(cf->cycle); + + if (old_cache && old_cache->inheritable) { + cn = ngx_ssl_cache_lookup(old_cache, type, &id, hash); + + if (cn != NULL) { + switch (id.type) { + + case NGX_SSL_CACHE_DATA: + value = type->ref(err, cn->value); + break; + + default: + if (rc != NGX_FILE_ERROR + && uniq == cn->uniq && mtime == cn->mtime) + { + value = type->ref(err, cn->value); + } + break; + } + } + } + + if (value == NULL) { + value = type->create(&id, err, data); + if (value == NULL) { + return NULL; + } + } + cn = ngx_palloc(cf->pool, sizeof(ngx_ssl_cache_node_t) + id.len + 1); if (cn == NULL) { + type->free(value); return NULL; } @@ -165,14 +245,12 @@ ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_uint_t index, char **err, cn->id.len = id.len; cn->id.type = id.type; cn->type = type; + cn->value = value; + cn->mtime = mtime; + cn->uniq = uniq; ngx_cpystrn(cn->id.data, id.data, id.len + 1); - cn->value = type->create(&id, err, data); - if (cn->value == NULL) { - return NULL; - } - ngx_rbtree_insert(&cache->rbtree, &cn->node); return type->ref(err, cn->value); @@ -729,6 +807,8 @@ ngx_openssl_cache_create_conf(ngx_cycle_t *cycle) return NULL; } + cache->inheritable = NGX_CONF_UNSET; + cln = ngx_pool_cleanup_add(cycle->pool, 0); if (cln == NULL) { return NULL; @@ -744,6 +824,17 @@ ngx_openssl_cache_create_conf(ngx_cycle_t *cycle) } +static char * +ngx_openssl_cache_init_conf(ngx_cycle_t *cycle, void *conf) +{ + ngx_ssl_cache_t *cache = conf; + + ngx_conf_init_value(cache->inheritable, 1); + + return NGX_CONF_OK; +} + + static void ngx_ssl_cache_cleanup(void *data) { From noreply at nginx.com Fri Jan 17 00:38:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 17 Jan 2025 00:38:02 +0000 (UTC) Subject: [nginx] SSL: caching certificates and certificate keys with variables. Message-ID: <20250117003802.4F8E448F16@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/0e756d67aa1e42e3b1b360936eb4d6c06bced2c1 branches: master commit: 0e756d67aa1e42e3b1b360936eb4d6c06bced2c1 user: Sergey Kandaurov date: Tue, 29 Oct 2024 16:25:11 +0400 description: SSL: caching certificates and certificate keys with variables. A new directive "ssl_certificate_cache max=N [valid=time] [inactive=time]" enables caching of SSL certificate chain and secret key objects specified by "ssl_certificate" and "ssl_certificate_key" directives with variables. Co-authored-by: Aleksei Bavshin --- src/event/ngx_event_openssl.c | 11 +- src/event/ngx_event_openssl.h | 12 +- src/event/ngx_event_openssl_cache.c | 240 +++++++++++++++++++++++++++++---- src/http/modules/ngx_http_ssl_module.c | 106 +++++++++++++++ src/http/modules/ngx_http_ssl_module.h | 2 + src/http/ngx_http_request.c | 1 + src/http/ngx_http_upstream.c | 2 +- src/stream/ngx_stream_proxy_module.c | 2 +- src/stream/ngx_stream_ssl_module.c | 107 +++++++++++++++ src/stream/ngx_stream_ssl_module.h | 66 ++++----- 10 files changed, 481 insertions(+), 68 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 35e9f3c88..8963c8124 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -562,15 +562,16 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, ngx_int_t ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool, - ngx_str_t *cert, ngx_str_t *key, ngx_array_t *passwords) + ngx_str_t *cert, ngx_str_t *key, ngx_ssl_cache_t *cache, + ngx_array_t *passwords) { char *err; X509 *x509; EVP_PKEY *pkey; STACK_OF(X509) *chain; - chain = ngx_ssl_cache_connection_fetch(pool, NGX_SSL_CACHE_CERT, &err, - cert, NULL); + chain = ngx_ssl_cache_connection_fetch(cache, pool, NGX_SSL_CACHE_CERT, + &err, cert, NULL); if (chain == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_ERR, c->log, 0, @@ -610,8 +611,8 @@ ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool, #endif - pkey = ngx_ssl_cache_connection_fetch(pool, NGX_SSL_CACHE_PKEY, &err, - key, passwords); + pkey = ngx_ssl_cache_connection_fetch(cache, pool, NGX_SSL_CACHE_PKEY, + &err, key, passwords); if (pkey == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_ERR, c->log, 0, diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 2147205d6..0713c5671 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -83,7 +83,8 @@ #endif -typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; +typedef struct ngx_ssl_cache_s ngx_ssl_cache_t; +typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; struct ngx_ssl_s { @@ -214,7 +215,8 @@ ngx_int_t ngx_ssl_certificates(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_int_t ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, ngx_str_t *key, ngx_array_t *passwords); ngx_int_t ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool, - ngx_str_t *cert, ngx_str_t *key, ngx_array_t *passwords); + ngx_str_t *cert, ngx_str_t *key, ngx_ssl_cache_t *cache, + ngx_array_t *passwords); ngx_int_t ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *ciphers, ngx_uint_t prefer_server_ciphers); @@ -237,10 +239,12 @@ ngx_int_t ngx_ssl_ocsp_get_status(ngx_connection_t *c, const char **s); void ngx_ssl_ocsp_cleanup(ngx_connection_t *c); ngx_int_t ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data); +ngx_ssl_cache_t *ngx_ssl_cache_init(ngx_pool_t *pool, ngx_uint_t max, + time_t valid, time_t inactive); void *ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_uint_t index, char **err, ngx_str_t *path, void *data); -void *ngx_ssl_cache_connection_fetch(ngx_pool_t *pool, ngx_uint_t index, - char **err, ngx_str_t *path, void *data); +void *ngx_ssl_cache_connection_fetch(ngx_ssl_cache_t *cache, ngx_pool_t *pool, + ngx_uint_t index, char **err, ngx_str_t *path, void *data); ngx_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file); ngx_array_t *ngx_ssl_preserve_passwords(ngx_conf_t *cf, diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c index c79f77456..7589e6c90 100644 --- a/src/event/ngx_event_openssl_cache.c +++ b/src/event/ngx_event_openssl_cache.c @@ -46,21 +46,31 @@ typedef struct { typedef struct { ngx_rbtree_node_t node; + ngx_queue_t queue; ngx_ssl_cache_key_t id; ngx_ssl_cache_type_t *type; void *value; + time_t created; + time_t accessed; + time_t mtime; ngx_file_uniq_t uniq; } ngx_ssl_cache_node_t; -typedef struct { +struct ngx_ssl_cache_s { ngx_rbtree_t rbtree; ngx_rbtree_node_t sentinel; + ngx_queue_t expire_queue; ngx_flag_t inheritable; -} ngx_ssl_cache_t; + + ngx_uint_t current; + ngx_uint_t max; + time_t valid; + time_t inactive; +}; typedef struct { @@ -73,6 +83,8 @@ static ngx_int_t ngx_ssl_cache_init_key(ngx_pool_t *pool, ngx_uint_t index, ngx_str_t *path, ngx_ssl_cache_key_t *id); static ngx_ssl_cache_node_t *ngx_ssl_cache_lookup(ngx_ssl_cache_t *cache, ngx_ssl_cache_type_t *type, ngx_ssl_cache_key_t *id, uint32_t hash); +static void ngx_ssl_cache_expire(ngx_ssl_cache_t *cache, ngx_uint_t n, + ngx_log_t *log); static void *ngx_ssl_cache_cert_create(ngx_ssl_cache_key_t *id, char **err, void *data); @@ -101,6 +113,8 @@ static char *ngx_openssl_cache_init_conf(ngx_cycle_t *cycle, void *conf); static void ngx_ssl_cache_cleanup(void *data); static void ngx_ssl_cache_node_insert(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); +static void ngx_ssl_cache_node_free(ngx_rbtree_t *rbtree, + ngx_ssl_cache_node_t *cn); static ngx_command_t ngx_openssl_cache_commands[] = { @@ -260,6 +274,8 @@ ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_uint_t index, char **err, ngx_cpystrn(cn->id.data, id.data, id.len + 1); + ngx_queue_init(&cn->queue); + ngx_rbtree_insert(&cache->rbtree, &cn->node); return type->ref(err, cn->value); @@ -267,10 +283,15 @@ ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_uint_t index, char **err, void * -ngx_ssl_cache_connection_fetch(ngx_pool_t *pool, ngx_uint_t index, char **err, - ngx_str_t *path, void *data) +ngx_ssl_cache_connection_fetch(ngx_ssl_cache_t *cache, ngx_pool_t *pool, + ngx_uint_t index, char **err, ngx_str_t *path, void *data) { - ngx_ssl_cache_key_t id; + void *value; + time_t now; + uint32_t hash; + ngx_ssl_cache_key_t id; + ngx_ssl_cache_type_t *type; + ngx_ssl_cache_node_t *cn; *err = NULL; @@ -278,7 +299,89 @@ ngx_ssl_cache_connection_fetch(ngx_pool_t *pool, ngx_uint_t index, char **err, return NULL; } - return ngx_ssl_cache_types[index].create(&id, err, &data); + type = &ngx_ssl_cache_types[index]; + + if (cache == NULL) { + return type->create(&id, err, &data); + } + + now = ngx_time(); + + hash = ngx_murmur_hash2(id.data, id.len); + + cn = ngx_ssl_cache_lookup(cache, type, &id, hash); + + if (cn != NULL) { + ngx_queue_remove(&cn->queue); + + if (id.type == NGX_SSL_CACHE_DATA) { + goto found; + } + + if (now - cn->created > cache->valid) { + ngx_log_debug1(NGX_LOG_DEBUG_CORE, pool->log, 0, + "update cached ssl object: %s", cn->id.data); + + type->free(cn->value); + + value = type->create(&id, err, &data); + + if (value == NULL || data == NGX_SSL_CACHE_DISABLED) { + ngx_rbtree_delete(&cache->rbtree, &cn->node); + + cache->current--; + + ngx_free(cn); + + return value; + } + + cn->value = value; + cn->created = now; + } + + goto found; + } + + value = type->create(&id, err, &data); + + if (value == NULL || data == NGX_SSL_CACHE_DISABLED) { + return value; + } + + cn = ngx_alloc(sizeof(ngx_ssl_cache_node_t) + id.len + 1, pool->log); + if (cn == NULL) { + type->free(value); + return NULL; + } + + cn->node.key = hash; + cn->id.data = (u_char *)(cn + 1); + cn->id.len = id.len; + cn->id.type = id.type; + cn->type = type; + cn->value = value; + cn->created = now; + + ngx_cpystrn(cn->id.data, id.data, id.len + 1); + + ngx_ssl_cache_expire(cache, 1, pool->log); + + if (cache->current >= cache->max) { + ngx_ssl_cache_expire(cache, 0, pool->log); + } + + ngx_rbtree_insert(&cache->rbtree, &cn->node); + + cache->current++; + +found: + + cn->accessed = now; + + ngx_queue_insert_head(&cache->expire_queue, &cn->queue); + + return type->ref(err, cn->value); } @@ -365,6 +468,37 @@ ngx_ssl_cache_lookup(ngx_ssl_cache_t *cache, ngx_ssl_cache_type_t *type, } +static void +ngx_ssl_cache_expire(ngx_ssl_cache_t *cache, ngx_uint_t n, + ngx_log_t *log) +{ + time_t now; + ngx_queue_t *q; + ngx_ssl_cache_node_t *cn; + + now = ngx_time(); + + while (n < 3) { + + if (ngx_queue_empty(&cache->expire_queue)) { + return; + } + + q = ngx_queue_last(&cache->expire_queue); + + cn = ngx_queue_data(q, ngx_ssl_cache_node_t, queue); + + if (n++ != 0 && now - cn->accessed <= cache->inactive) { + return; + } + + ngx_ssl_cache_node_free(&cache->rbtree, cn); + + cache->current--; + } +} + + static void * ngx_ssl_cache_cert_create(ngx_ssl_cache_key_t *id, char **err, void *data) { @@ -822,27 +956,15 @@ ngx_ssl_cache_create_bio(ngx_ssl_cache_key_t *id, char **err) static void * ngx_openssl_cache_create_conf(ngx_cycle_t *cycle) { - ngx_ssl_cache_t *cache; - ngx_pool_cleanup_t *cln; + ngx_ssl_cache_t *cache; - cache = ngx_pcalloc(cycle->pool, sizeof(ngx_ssl_cache_t)); + cache = ngx_ssl_cache_init(cycle->pool, 0, 0, 0); if (cache == NULL) { return NULL; } cache->inheritable = NGX_CONF_UNSET; - cln = ngx_pool_cleanup_add(cycle->pool, 0); - if (cln == NULL) { - return NULL; - } - - cln->handler = ngx_ssl_cache_cleanup; - cln->data = cache; - - ngx_rbtree_init(&cache->rbtree, &cache->sentinel, - ngx_ssl_cache_node_insert); - return cache; } @@ -858,6 +980,39 @@ ngx_openssl_cache_init_conf(ngx_cycle_t *cycle, void *conf) } +ngx_ssl_cache_t * +ngx_ssl_cache_init(ngx_pool_t *pool, ngx_uint_t max, time_t valid, + time_t inactive) +{ + ngx_ssl_cache_t *cache; + ngx_pool_cleanup_t *cln; + + cache = ngx_pcalloc(pool, sizeof(ngx_ssl_cache_t)); + if (cache == NULL) { + return NULL; + } + + ngx_rbtree_init(&cache->rbtree, &cache->sentinel, + ngx_ssl_cache_node_insert); + + ngx_queue_init(&cache->expire_queue); + + cache->max = max; + cache->valid = valid; + cache->inactive = inactive; + + cln = ngx_pool_cleanup_add(pool, 0); + if (cln == NULL) { + return NULL; + } + + cln->handler = ngx_ssl_cache_cleanup; + cln->data = cache; + + return cache; +} + + static void ngx_ssl_cache_cleanup(void *data) { @@ -873,12 +1028,47 @@ ngx_ssl_cache_cleanup(void *data) return; } - for (node = ngx_rbtree_min(tree->root, tree->sentinel); - node; - node = ngx_rbtree_next(tree, node)) - { + node = ngx_rbtree_min(tree->root, tree->sentinel); + + while (node != NULL) { cn = ngx_rbtree_data(node, ngx_ssl_cache_node_t, node); - cn->type->free(cn->value); + node = ngx_rbtree_next(tree, node); + + ngx_ssl_cache_node_free(tree, cn); + + if (cache->max) { + cache->current--; + } + } + + if (cache->current) { + ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, + "%ui items still left in ssl cache", + cache->current); + } + + if (!ngx_queue_empty(&cache->expire_queue)) { + ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, + "queue still is not empty in ssl cache"); + + } +} + + +static void +ngx_ssl_cache_node_free(ngx_rbtree_t *rbtree, ngx_ssl_cache_node_t *cn) +{ + cn->type->free(cn->value); + + ngx_rbtree_delete(rbtree, &cn->node); + + if (!ngx_queue_empty(&cn->queue)) { + ngx_queue_remove(&cn->queue); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, + "delete cached ssl object: %s", cn->id.data); + + ngx_free(cn); } } diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index 0e892b04d..dbfe5c08b 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -43,6 +43,8 @@ static char *ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, static ngx_int_t ngx_http_ssl_compile_certificates(ngx_conf_t *cf, ngx_http_ssl_srv_conf_t *conf); +static char *ngx_http_ssl_certificate_cache(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static char *ngx_http_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, @@ -108,6 +110,13 @@ static ngx_command_t ngx_http_ssl_commands[] = { offsetof(ngx_http_ssl_srv_conf_t, certificate_keys), NULL }, + { ngx_string("ssl_certificate_cache"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE123, + ngx_http_ssl_certificate_cache, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + { ngx_string("ssl_password_file"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_http_ssl_password_file, @@ -619,6 +628,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf) sscf->verify_depth = NGX_CONF_UNSET_UINT; sscf->certificates = NGX_CONF_UNSET_PTR; sscf->certificate_keys = NGX_CONF_UNSET_PTR; + sscf->certificate_cache = NGX_CONF_UNSET_PTR; sscf->passwords = NGX_CONF_UNSET_PTR; sscf->conf_commands = NGX_CONF_UNSET_PTR; sscf->builtin_session_cache = NGX_CONF_UNSET; @@ -664,6 +674,9 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_ptr_value(conf->certificate_keys, prev->certificate_keys, NULL); + ngx_conf_merge_ptr_value(conf->certificate_cache, prev->certificate_cache, + NULL); + ngx_conf_merge_ptr_value(conf->passwords, prev->passwords, NULL); ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, ""); @@ -984,6 +997,99 @@ found: } +static char * +ngx_http_ssl_certificate_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_ssl_srv_conf_t *sscf = conf; + + time_t inactive, valid; + ngx_str_t *value, s; + ngx_int_t max; + ngx_uint_t i; + + if (sscf->certificate_cache != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + max = 0; + inactive = 10; + valid = 60; + + for (i = 1; i < cf->args->nelts; i++) { + + if (ngx_strncmp(value[i].data, "max=", 4) == 0) { + + max = ngx_atoi(value[i].data + 4, value[i].len - 4); + if (max <= 0) { + goto failed; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) { + + s.len = value[i].len - 9; + s.data = value[i].data + 9; + + inactive = ngx_parse_time(&s, 1); + if (inactive == (time_t) NGX_ERROR) { + goto failed; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "valid=", 6) == 0) { + + s.len = value[i].len - 6; + s.data = value[i].data + 6; + + valid = ngx_parse_time(&s, 1); + if (valid == (time_t) NGX_ERROR) { + goto failed; + } + + continue; + } + + if (ngx_strcmp(value[i].data, "off") == 0) { + + sscf->certificate_cache = NULL; + + continue; + } + + failed: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + if (sscf->certificate_cache == NULL) { + return NGX_CONF_OK; + } + + if (max == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"ssl_certificate_cache\" must have " + "the \"max\" parameter"); + return NGX_CONF_ERROR; + } + + sscf->certificate_cache = ngx_ssl_cache_init(cf->pool, max, valid, + inactive); + if (sscf->certificate_cache == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + static char * ngx_http_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h index c69c8ffd2..8650fab93 100644 --- a/src/http/modules/ngx_http_ssl_module.h +++ b/src/http/modules/ngx_http_ssl_module.h @@ -38,6 +38,8 @@ typedef struct { ngx_array_t *certificate_values; ngx_array_t *certificate_key_values; + ngx_ssl_cache_t *certificate_cache; + ngx_str_t dhparam; ngx_str_t ecdh_curve; ngx_str_t client_certificate; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 3cca57cf5..f44c9e79b 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -1054,6 +1054,7 @@ ngx_http_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg) "ssl key: \"%s\"", key.data); if (ngx_ssl_connection_certificate(c, r->pool, &cert, &key, + sscf->certificate_cache, sscf->passwords) != NGX_OK) { diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index d95662c56..e6382ef79 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -2018,7 +2018,7 @@ ngx_http_upstream_ssl_certificate(ngx_http_request_t *r, ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream ssl key: \"%s\"", key.data); - if (ngx_ssl_connection_certificate(c, r->pool, &cert, &key, + if (ngx_ssl_connection_certificate(c, r->pool, &cert, &key, NULL, u->conf->ssl_passwords) != NGX_OK) { diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c index e978056ef..21b579af3 100644 --- a/src/stream/ngx_stream_proxy_module.c +++ b/src/stream/ngx_stream_proxy_module.c @@ -1324,7 +1324,7 @@ ngx_stream_proxy_ssl_certificate(ngx_stream_session_t *s) ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream upstream ssl key: \"%s\"", key.data); - if (ngx_ssl_connection_certificate(c, c->pool, &cert, &key, + if (ngx_ssl_connection_certificate(c, c->pool, &cert, &key, NULL, pscf->ssl_passwords) != NGX_OK) { diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c index dfbaa0e2f..b84995d61 100644 --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -47,6 +47,8 @@ static char *ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, static ngx_int_t ngx_stream_ssl_compile_certificates(ngx_conf_t *cf, ngx_stream_ssl_srv_conf_t *conf); +static char *ngx_stream_ssl_certificate_cache(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); static char *ngx_stream_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, @@ -117,6 +119,13 @@ static ngx_command_t ngx_stream_ssl_commands[] = { offsetof(ngx_stream_ssl_srv_conf_t, certificate_keys), NULL }, + { ngx_string("ssl_certificate_cache"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE123, + ngx_stream_ssl_certificate_cache, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + { ngx_string("ssl_password_file"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_stream_ssl_password_file, @@ -718,6 +727,7 @@ ngx_stream_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg) "ssl key: \"%s\"", key.data); if (ngx_ssl_connection_certificate(c, c->pool, &cert, &key, + sscf->certificate_cache, sscf->passwords) != NGX_OK) { @@ -844,6 +854,7 @@ ngx_stream_ssl_create_srv_conf(ngx_conf_t *cf) sscf->handshake_timeout = NGX_CONF_UNSET_MSEC; sscf->certificates = NGX_CONF_UNSET_PTR; sscf->certificate_keys = NGX_CONF_UNSET_PTR; + sscf->certificate_cache = NGX_CONF_UNSET_PTR; sscf->passwords = NGX_CONF_UNSET_PTR; sscf->conf_commands = NGX_CONF_UNSET_PTR; sscf->prefer_server_ciphers = NGX_CONF_UNSET; @@ -892,6 +903,9 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_ptr_value(conf->certificate_keys, prev->certificate_keys, NULL); + ngx_conf_merge_ptr_value(conf->certificate_cache, prev->certificate_cache, + NULL); + ngx_conf_merge_ptr_value(conf->passwords, prev->passwords, NULL); ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, ""); @@ -1202,6 +1216,99 @@ found: } +static char * +ngx_stream_ssl_certificate_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_ssl_srv_conf_t *sscf = conf; + + time_t inactive, valid; + ngx_str_t *value, s; + ngx_int_t max; + ngx_uint_t i; + + if (sscf->certificate_cache != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + max = 0; + inactive = 10; + valid = 60; + + for (i = 1; i < cf->args->nelts; i++) { + + if (ngx_strncmp(value[i].data, "max=", 4) == 0) { + + max = ngx_atoi(value[i].data + 4, value[i].len - 4); + if (max <= 0) { + goto failed; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) { + + s.len = value[i].len - 9; + s.data = value[i].data + 9; + + inactive = ngx_parse_time(&s, 1); + if (inactive == (time_t) NGX_ERROR) { + goto failed; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "valid=", 6) == 0) { + + s.len = value[i].len - 6; + s.data = value[i].data + 6; + + valid = ngx_parse_time(&s, 1); + if (valid == (time_t) NGX_ERROR) { + goto failed; + } + + continue; + } + + if (ngx_strcmp(value[i].data, "off") == 0) { + + sscf->certificate_cache = NULL; + + continue; + } + + failed: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + if (sscf->certificate_cache == NULL) { + return NGX_CONF_OK; + } + + if (max == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"ssl_certificate_cache\" must have " + "the \"max\" parameter"); + return NGX_CONF_ERROR; + } + + sscf->certificate_cache = ngx_ssl_cache_init(cf->pool, max, valid, + inactive); + if (sscf->certificate_cache == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + static char * ngx_stream_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { diff --git a/src/stream/ngx_stream_ssl_module.h b/src/stream/ngx_stream_ssl_module.h index e6769426c..ffa03a6f3 100644 --- a/src/stream/ngx_stream_ssl_module.h +++ b/src/stream/ngx_stream_ssl_module.h @@ -15,53 +15,55 @@ typedef struct { - ngx_msec_t handshake_timeout; + ngx_msec_t handshake_timeout; - ngx_flag_t prefer_server_ciphers; - ngx_flag_t reject_handshake; + ngx_flag_t prefer_server_ciphers; + ngx_flag_t reject_handshake; - ngx_ssl_t ssl; + ngx_ssl_t ssl; - ngx_uint_t protocols; + ngx_uint_t protocols; - ngx_uint_t verify; - ngx_uint_t verify_depth; + ngx_uint_t verify; + ngx_uint_t verify_depth; - ssize_t builtin_session_cache; + ssize_t builtin_session_cache; - time_t session_timeout; + time_t session_timeout; - ngx_array_t *certificates; - ngx_array_t *certificate_keys; + ngx_array_t *certificates; + ngx_array_t *certificate_keys; - ngx_array_t *certificate_values; - ngx_array_t *certificate_key_values; + ngx_array_t *certificate_values; + ngx_array_t *certificate_key_values; - ngx_str_t dhparam; - ngx_str_t ecdh_curve; - ngx_str_t client_certificate; - ngx_str_t trusted_certificate; - ngx_str_t crl; - ngx_str_t alpn; + ngx_ssl_cache_t *certificate_cache; - ngx_str_t ciphers; + ngx_str_t dhparam; + ngx_str_t ecdh_curve; + ngx_str_t client_certificate; + ngx_str_t trusted_certificate; + ngx_str_t crl; + ngx_str_t alpn; - ngx_array_t *passwords; - ngx_array_t *conf_commands; + ngx_str_t ciphers; - ngx_shm_zone_t *shm_zone; + ngx_array_t *passwords; + ngx_array_t *conf_commands; - ngx_flag_t session_tickets; - ngx_array_t *session_ticket_keys; + ngx_shm_zone_t *shm_zone; - ngx_uint_t ocsp; - ngx_str_t ocsp_responder; - ngx_shm_zone_t *ocsp_cache_zone; + ngx_flag_t session_tickets; + ngx_array_t *session_ticket_keys; - ngx_flag_t stapling; - ngx_flag_t stapling_verify; - ngx_str_t stapling_file; - ngx_str_t stapling_responder; + ngx_uint_t ocsp; + ngx_str_t ocsp_responder; + ngx_shm_zone_t *ocsp_cache_zone; + + ngx_flag_t stapling; + ngx_flag_t stapling_verify; + ngx_str_t stapling_file; + ngx_str_t stapling_responder; } ngx_stream_ssl_srv_conf_t; From noreply at nginx.com Fri Jan 17 00:38:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 17 Jan 2025 00:38:02 +0000 (UTC) Subject: [nginx] Upstream: caching certificates and certificate keys with variables. Message-ID: <20250117003802.5FC0E48F1A@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/454ad0ef33a347eba1a62d18c8fc0498f4dcfd64 branches: master commit: 454ad0ef33a347eba1a62d18c8fc0498f4dcfd64 user: Sergey Kandaurov date: Tue, 29 Oct 2024 18:20:53 +0400 description: Upstream: caching certificates and certificate keys with variables. Caching is enabled with proxy_ssl_certificate_cache and friends. Co-authored-by: Aleksei Bavshin --- src/http/modules/ngx_http_grpc_module.c | 106 +++++++++++++++++++++++++++++ src/http/modules/ngx_http_proxy_module.c | 106 +++++++++++++++++++++++++++++ src/http/modules/ngx_http_uwsgi_module.c | 106 +++++++++++++++++++++++++++++ src/http/ngx_http_upstream.c | 3 +- src/http/ngx_http_upstream.h | 1 + src/stream/ngx_stream_proxy_module.c | 111 ++++++++++++++++++++++++++++++- 6 files changed, 431 insertions(+), 2 deletions(-) diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c index 326720447..8e246c3cf 100644 --- a/src/http/modules/ngx_http_grpc_module.c +++ b/src/http/modules/ngx_http_grpc_module.c @@ -205,6 +205,8 @@ static char *ngx_http_grpc_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); #if (NGX_HTTP_SSL) +static char *ngx_http_grpc_ssl_certificate_cache(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); static char *ngx_http_grpc_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_grpc_ssl_conf_command_check(ngx_conf_t *cf, void *post, @@ -437,6 +439,13 @@ static ngx_command_t ngx_http_grpc_commands[] = { offsetof(ngx_http_grpc_loc_conf_t, upstream.ssl_certificate_key), NULL }, + { ngx_string("grpc_ssl_certificate_cache"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, + ngx_http_grpc_ssl_certificate_cache, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + { ngx_string("grpc_ssl_password_file"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_grpc_ssl_password_file, @@ -4386,6 +4395,7 @@ ngx_http_grpc_create_loc_conf(ngx_conf_t *cf) conf->ssl_verify_depth = NGX_CONF_UNSET_UINT; conf->upstream.ssl_certificate = NGX_CONF_UNSET_PTR; conf->upstream.ssl_certificate_key = NGX_CONF_UNSET_PTR; + conf->upstream.ssl_certificate_cache = NGX_CONF_UNSET_PTR; conf->upstream.ssl_passwords = NGX_CONF_UNSET_PTR; conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; #endif @@ -4497,6 +4507,8 @@ ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) prev->upstream.ssl_certificate, NULL); ngx_conf_merge_ptr_value(conf->upstream.ssl_certificate_key, prev->upstream.ssl_certificate_key, NULL); + ngx_conf_merge_ptr_value(conf->upstream.ssl_certificate_cache, + prev->upstream.ssl_certificate_cache, NULL); ngx_conf_merge_ptr_value(conf->upstream.ssl_passwords, prev->upstream.ssl_passwords, NULL); @@ -4847,6 +4859,100 @@ ngx_http_grpc_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #if (NGX_HTTP_SSL) +static char * +ngx_http_grpc_ssl_certificate_cache(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + ngx_http_grpc_loc_conf_t *plcf = conf; + + time_t inactive, valid; + ngx_str_t *value, s; + ngx_int_t max; + ngx_uint_t i; + + if (plcf->upstream.ssl_certificate_cache != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + max = 0; + inactive = 10; + valid = 60; + + for (i = 1; i < cf->args->nelts; i++) { + + if (ngx_strncmp(value[i].data, "max=", 4) == 0) { + + max = ngx_atoi(value[i].data + 4, value[i].len - 4); + if (max <= 0) { + goto failed; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) { + + s.len = value[i].len - 9; + s.data = value[i].data + 9; + + inactive = ngx_parse_time(&s, 1); + if (inactive == (time_t) NGX_ERROR) { + goto failed; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "valid=", 6) == 0) { + + s.len = value[i].len - 6; + s.data = value[i].data + 6; + + valid = ngx_parse_time(&s, 1); + if (valid == (time_t) NGX_ERROR) { + goto failed; + } + + continue; + } + + if (ngx_strcmp(value[i].data, "off") == 0) { + + plcf->upstream.ssl_certificate_cache = NULL; + + continue; + } + + failed: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + if (plcf->upstream.ssl_certificate_cache == NULL) { + return NGX_CONF_OK; + } + + if (max == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"grpc_ssl_certificate_cache\" must have " + "the \"max\" parameter"); + return NGX_CONF_ERROR; + } + + plcf->upstream.ssl_certificate_cache = ngx_ssl_cache_init(cf->pool, max, + valid, inactive); + if (plcf->upstream.ssl_certificate_cache == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + static char * ngx_http_grpc_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index 855ef523d..27c34fef2 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -226,6 +226,8 @@ static char *ngx_http_proxy_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); #endif #if (NGX_HTTP_SSL) +static char *ngx_http_proxy_ssl_certificate_cache(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); static char *ngx_http_proxy_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); #endif @@ -775,6 +777,13 @@ static ngx_command_t ngx_http_proxy_commands[] = { offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_certificate_key), NULL }, + { ngx_string("proxy_ssl_certificate_cache"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, + ngx_http_proxy_ssl_certificate_cache, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + { ngx_string("proxy_ssl_password_file"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_proxy_ssl_password_file, @@ -3613,6 +3622,7 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf) conf->upstream.ssl_verify = NGX_CONF_UNSET; conf->upstream.ssl_certificate = NGX_CONF_UNSET_PTR; conf->upstream.ssl_certificate_key = NGX_CONF_UNSET_PTR; + conf->upstream.ssl_certificate_cache = NGX_CONF_UNSET_PTR; conf->upstream.ssl_passwords = NGX_CONF_UNSET_PTR; conf->ssl_verify_depth = NGX_CONF_UNSET_UINT; conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; @@ -3964,6 +3974,8 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) prev->upstream.ssl_certificate, NULL); ngx_conf_merge_ptr_value(conf->upstream.ssl_certificate_key, prev->upstream.ssl_certificate_key, NULL); + ngx_conf_merge_ptr_value(conf->upstream.ssl_certificate_cache, + prev->upstream.ssl_certificate_cache, NULL); ngx_conf_merge_ptr_value(conf->upstream.ssl_passwords, prev->upstream.ssl_passwords, NULL); @@ -5074,6 +5086,100 @@ ngx_http_proxy_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #if (NGX_HTTP_SSL) +static char * +ngx_http_proxy_ssl_certificate_cache(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + ngx_http_proxy_loc_conf_t *plcf = conf; + + time_t inactive, valid; + ngx_str_t *value, s; + ngx_int_t max; + ngx_uint_t i; + + if (plcf->upstream.ssl_certificate_cache != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + max = 0; + inactive = 10; + valid = 60; + + for (i = 1; i < cf->args->nelts; i++) { + + if (ngx_strncmp(value[i].data, "max=", 4) == 0) { + + max = ngx_atoi(value[i].data + 4, value[i].len - 4); + if (max <= 0) { + goto failed; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) { + + s.len = value[i].len - 9; + s.data = value[i].data + 9; + + inactive = ngx_parse_time(&s, 1); + if (inactive == (time_t) NGX_ERROR) { + goto failed; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "valid=", 6) == 0) { + + s.len = value[i].len - 6; + s.data = value[i].data + 6; + + valid = ngx_parse_time(&s, 1); + if (valid == (time_t) NGX_ERROR) { + goto failed; + } + + continue; + } + + if (ngx_strcmp(value[i].data, "off") == 0) { + + plcf->upstream.ssl_certificate_cache = NULL; + + continue; + } + + failed: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + if (plcf->upstream.ssl_certificate_cache == NULL) { + return NGX_CONF_OK; + } + + if (max == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"proxy_ssl_certificate_cache\" must have " + "the \"max\" parameter"); + return NGX_CONF_ERROR; + } + + plcf->upstream.ssl_certificate_cache = ngx_ssl_cache_init(cf->pool, max, + valid, inactive); + if (plcf->upstream.ssl_certificate_cache == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + static char * ngx_http_proxy_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { diff --git a/src/http/modules/ngx_http_uwsgi_module.c b/src/http/modules/ngx_http_uwsgi_module.c index 7988cc589..14aae5bf1 100644 --- a/src/http/modules/ngx_http_uwsgi_module.c +++ b/src/http/modules/ngx_http_uwsgi_module.c @@ -92,6 +92,8 @@ static char *ngx_http_uwsgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, #endif #if (NGX_HTTP_SSL) +static char *ngx_http_uwsgi_ssl_certificate_cache(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); static char *ngx_http_uwsgi_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_uwsgi_ssl_conf_command_check(ngx_conf_t *cf, void *post, @@ -559,6 +561,13 @@ static ngx_command_t ngx_http_uwsgi_commands[] = { offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ssl_certificate_key), NULL }, + { ngx_string("uwsgi_ssl_certificate_cache"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, + ngx_http_uwsgi_ssl_certificate_cache, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + { ngx_string("uwsgi_ssl_password_file"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_uwsgi_ssl_password_file, @@ -1590,6 +1599,7 @@ ngx_http_uwsgi_create_loc_conf(ngx_conf_t *cf) conf->ssl_verify_depth = NGX_CONF_UNSET_UINT; conf->upstream.ssl_certificate = NGX_CONF_UNSET_PTR; conf->upstream.ssl_certificate_key = NGX_CONF_UNSET_PTR; + conf->upstream.ssl_certificate_cache = NGX_CONF_UNSET_PTR; conf->upstream.ssl_passwords = NGX_CONF_UNSET_PTR; conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; #endif @@ -1921,6 +1931,8 @@ ngx_http_uwsgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) prev->upstream.ssl_certificate, NULL); ngx_conf_merge_ptr_value(conf->upstream.ssl_certificate_key, prev->upstream.ssl_certificate_key, NULL); + ngx_conf_merge_ptr_value(conf->upstream.ssl_certificate_cache, + prev->upstream.ssl_certificate_cache, NULL); ngx_conf_merge_ptr_value(conf->upstream.ssl_passwords, prev->upstream.ssl_passwords, NULL); @@ -2455,6 +2467,100 @@ ngx_http_uwsgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #if (NGX_HTTP_SSL) +static char * +ngx_http_uwsgi_ssl_certificate_cache(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + ngx_http_uwsgi_loc_conf_t *plcf = conf; + + time_t inactive, valid; + ngx_str_t *value, s; + ngx_int_t max; + ngx_uint_t i; + + if (plcf->upstream.ssl_certificate_cache != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + max = 0; + inactive = 10; + valid = 60; + + for (i = 1; i < cf->args->nelts; i++) { + + if (ngx_strncmp(value[i].data, "max=", 4) == 0) { + + max = ngx_atoi(value[i].data + 4, value[i].len - 4); + if (max <= 0) { + goto failed; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) { + + s.len = value[i].len - 9; + s.data = value[i].data + 9; + + inactive = ngx_parse_time(&s, 1); + if (inactive == (time_t) NGX_ERROR) { + goto failed; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "valid=", 6) == 0) { + + s.len = value[i].len - 6; + s.data = value[i].data + 6; + + valid = ngx_parse_time(&s, 1); + if (valid == (time_t) NGX_ERROR) { + goto failed; + } + + continue; + } + + if (ngx_strcmp(value[i].data, "off") == 0) { + + plcf->upstream.ssl_certificate_cache = NULL; + + continue; + } + + failed: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + if (plcf->upstream.ssl_certificate_cache == NULL) { + return NGX_CONF_OK; + } + + if (max == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"uwsgi_ssl_certificate_cache\" must have " + "the \"max\" parameter"); + return NGX_CONF_ERROR; + } + + plcf->upstream.ssl_certificate_cache = ngx_ssl_cache_init(cf->pool, max, + valid, inactive); + if (plcf->upstream.ssl_certificate_cache == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + static char * ngx_http_uwsgi_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index e6382ef79..77dc032f2 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -2018,7 +2018,8 @@ ngx_http_upstream_ssl_certificate(ngx_http_request_t *r, ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream ssl key: \"%s\"", key.data); - if (ngx_ssl_connection_certificate(c, r->pool, &cert, &key, NULL, + if (ngx_ssl_connection_certificate(c, r->pool, &cert, &key, + u->conf->ssl_certificate_cache, u->conf->ssl_passwords) != NGX_OK) { diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h index 57ee06f8d..069c0f7a4 100644 --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h @@ -245,6 +245,7 @@ typedef struct { ngx_http_complex_value_t *ssl_certificate; ngx_http_complex_value_t *ssl_certificate_key; + ngx_ssl_cache_t *ssl_certificate_cache; ngx_array_t *ssl_passwords; #endif diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c index 21b579af3..7f8bfc4e0 100644 --- a/src/stream/ngx_stream_proxy_module.c +++ b/src/stream/ngx_stream_proxy_module.c @@ -49,6 +49,7 @@ typedef struct { ngx_str_t ssl_crl; ngx_stream_complex_value_t *ssl_certificate; ngx_stream_complex_value_t *ssl_certificate_key; + ngx_ssl_cache_t *ssl_certificate_cache; ngx_array_t *ssl_passwords; ngx_array_t *ssl_conf_commands; @@ -94,6 +95,8 @@ static char *ngx_stream_proxy_bind(ngx_conf_t *cf, ngx_command_t *cmd, #if (NGX_STREAM_SSL) static ngx_int_t ngx_stream_proxy_send_proxy_protocol(ngx_stream_session_t *s); +static char *ngx_stream_proxy_ssl_certificate_cache(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); static char *ngx_stream_proxy_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_stream_proxy_ssl_conf_command_check(ngx_conf_t *cf, void *post, @@ -341,6 +344,13 @@ static ngx_command_t ngx_stream_proxy_commands[] = { offsetof(ngx_stream_proxy_srv_conf_t, ssl_certificate_key), NULL }, + { ngx_string("proxy_ssl_certificate_cache"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE123, + ngx_stream_proxy_ssl_certificate_cache, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + { ngx_string("proxy_ssl_password_file"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_stream_proxy_ssl_password_file, @@ -1029,6 +1039,100 @@ ngx_stream_proxy_send_proxy_protocol(ngx_stream_session_t *s) } +static char * +ngx_stream_proxy_ssl_certificate_cache(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + ngx_stream_proxy_srv_conf_t *pscf = conf; + + time_t inactive, valid; + ngx_str_t *value, s; + ngx_int_t max; + ngx_uint_t i; + + if (pscf->ssl_certificate_cache != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + max = 0; + inactive = 10; + valid = 60; + + for (i = 1; i < cf->args->nelts; i++) { + + if (ngx_strncmp(value[i].data, "max=", 4) == 0) { + + max = ngx_atoi(value[i].data + 4, value[i].len - 4); + if (max <= 0) { + goto failed; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) { + + s.len = value[i].len - 9; + s.data = value[i].data + 9; + + inactive = ngx_parse_time(&s, 1); + if (inactive == (time_t) NGX_ERROR) { + goto failed; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "valid=", 6) == 0) { + + s.len = value[i].len - 6; + s.data = value[i].data + 6; + + valid = ngx_parse_time(&s, 1); + if (valid == (time_t) NGX_ERROR) { + goto failed; + } + + continue; + } + + if (ngx_strcmp(value[i].data, "off") == 0) { + + pscf->ssl_certificate_cache = NULL; + + continue; + } + + failed: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + if (pscf->ssl_certificate_cache == NULL) { + return NGX_CONF_OK; + } + + if (max == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"proxy_ssl_certificate_cache\" must have " + "the \"max\" parameter"); + return NGX_CONF_ERROR; + } + + pscf->ssl_certificate_cache = ngx_ssl_cache_init(cf->pool, max, valid, + inactive); + if (pscf->ssl_certificate_cache == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + static char * ngx_stream_proxy_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) @@ -1324,7 +1428,8 @@ ngx_stream_proxy_ssl_certificate(ngx_stream_session_t *s) ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream upstream ssl key: \"%s\"", key.data); - if (ngx_ssl_connection_certificate(c, c->pool, &cert, &key, NULL, + if (ngx_ssl_connection_certificate(c, c->pool, &cert, &key, + pscf->ssl_certificate_cache, pscf->ssl_passwords) != NGX_OK) { @@ -2120,6 +2225,7 @@ ngx_stream_proxy_create_srv_conf(ngx_conf_t *cf) conf->ssl_verify_depth = NGX_CONF_UNSET_UINT; conf->ssl_certificate = NGX_CONF_UNSET_PTR; conf->ssl_certificate_key = NGX_CONF_UNSET_PTR; + conf->ssl_certificate_cache = NGX_CONF_UNSET_PTR; conf->ssl_passwords = NGX_CONF_UNSET_PTR; conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; #endif @@ -2206,6 +2312,9 @@ ngx_stream_proxy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_ptr_value(conf->ssl_certificate_key, prev->ssl_certificate_key, NULL); + ngx_conf_merge_ptr_value(conf->ssl_certificate_cache, + prev->ssl_certificate_cache, NULL); + ngx_conf_merge_ptr_value(conf->ssl_passwords, prev->ssl_passwords, NULL); ngx_conf_merge_ptr_value(conf->ssl_conf_commands, From noreply at nginx.com Fri Jan 17 00:38:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 17 Jan 2025 00:38:02 +0000 (UTC) Subject: [nginx] SSL: avoid using mismatched certificate/key cached pairs. Message-ID: <20250117003802.663EB48F1B@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/5d5d9adccfeaff7d5926737ee5dfa43937fe5899 branches: master commit: 5d5d9adccfeaff7d5926737ee5dfa43937fe5899 user: Sergey Kandaurov date: Wed, 8 Jan 2025 17:50:33 +0400 description: SSL: avoid using mismatched certificate/key cached pairs. This can happen with certificates and certificate keys specified with variables due to partial cache update in various scenarios: - cache expiration with only one element of pair evicted - on-disk update with non-cacheable encrypted keys - non-atomic on-disk update The fix is to retry with fresh data on X509_R_KEY_VALUES_MISMATCH. --- src/event/ngx_event_openssl.c | 28 +++++++++++++++++++++++++--- src/event/ngx_event_openssl.h | 2 ++ src/event/ngx_event_openssl_cache.c | 9 +++++++-- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 8963c8124..0681ca3a2 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -567,10 +567,17 @@ ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool, { char *err; X509 *x509; + u_long n; EVP_PKEY *pkey; + ngx_uint_t mask; STACK_OF(X509) *chain; - chain = ngx_ssl_cache_connection_fetch(cache, pool, NGX_SSL_CACHE_CERT, + mask = 0; + +retry: + + chain = ngx_ssl_cache_connection_fetch(cache, pool, + NGX_SSL_CACHE_CERT | mask, &err, cert, NULL); if (chain == NULL) { if (err != NULL) { @@ -611,7 +618,8 @@ ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool, #endif - pkey = ngx_ssl_cache_connection_fetch(cache, pool, NGX_SSL_CACHE_PKEY, + pkey = ngx_ssl_cache_connection_fetch(cache, pool, + NGX_SSL_CACHE_PKEY | mask, &err, key, passwords); if (pkey == NULL) { if (err != NULL) { @@ -624,9 +632,23 @@ ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool, } if (SSL_use_PrivateKey(c->ssl->connection, pkey) == 0) { + EVP_PKEY_free(pkey); + + /* there can be mismatched pairs on uneven cache update */ + + n = ERR_peek_last_error(); + + if (ERR_GET_LIB(n) == ERR_LIB_X509 + && ERR_GET_REASON(n) == X509_R_KEY_VALUES_MISMATCH + && mask == 0) + { + ERR_clear_error(); + mask = NGX_SSL_CACHE_INVALIDATE; + goto retry; + } + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_use_PrivateKey(\"%s\") failed", key->data); - EVP_PKEY_free(pkey); return NGX_ERROR; } diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 0713c5671..c9dc50c75 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -206,6 +206,8 @@ typedef struct { #define NGX_SSL_CACHE_CRL 2 #define NGX_SSL_CACHE_CA 3 +#define NGX_SSL_CACHE_INVALIDATE 0x80000000 + ngx_int_t ngx_ssl_init(ngx_log_t *log); ngx_int_t ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data); diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c index eb03e16b2..d62b4c430 100644 --- a/src/event/ngx_event_openssl_cache.c +++ b/src/event/ngx_event_openssl_cache.c @@ -289,6 +289,7 @@ ngx_ssl_cache_connection_fetch(ngx_ssl_cache_t *cache, ngx_pool_t *pool, void *value; time_t now; uint32_t hash; + ngx_uint_t invalidate; ngx_file_info_t fi; ngx_ssl_cache_key_t id; ngx_ssl_cache_type_t *type; @@ -296,6 +297,9 @@ ngx_ssl_cache_connection_fetch(ngx_ssl_cache_t *cache, ngx_pool_t *pool, *err = NULL; + invalidate = index & NGX_SSL_CACHE_INVALIDATE; + index &= ~NGX_SSL_CACHE_INVALIDATE; + if (ngx_ssl_cache_init_key(pool, index, path, &id) != NGX_OK) { return NULL; } @@ -319,7 +323,7 @@ ngx_ssl_cache_connection_fetch(ngx_ssl_cache_t *cache, ngx_pool_t *pool, goto found; } - if (now - cn->created <= cache->valid) { + if (!invalidate && now - cn->created <= cache->valid) { goto found; } @@ -329,7 +333,8 @@ ngx_ssl_cache_connection_fetch(ngx_ssl_cache_t *cache, ngx_pool_t *pool, if (ngx_file_info(id.data, &fi) != NGX_FILE_ERROR) { - if (ngx_file_uniq(&fi) == cn->uniq + if (!invalidate + && ngx_file_uniq(&fi) == cn->uniq && ngx_file_mtime(&fi) == cn->mtime) { break;