From e172890d65047e29b60a421a61d9ba69a98fb87c Mon Sep 17 00:00:00 2001 From: willmafh Date: Tue, 14 Oct 2025 22:52:31 +0800 Subject: [PATCH 1/3] feature: proxy_ssl_certificate_by_lua directives --- README.md | 2 + config | 2 + src/ngx_stream_lua_common.h | 5 + src/ngx_stream_lua_control.c | 2 + src/ngx_stream_lua_coroutine.c | 3 + src/ngx_stream_lua_module.c | 31 + src/ngx_stream_lua_phase.c | 4 + src/ngx_stream_lua_proxy_ssl_certby.c | 1004 +++++++++++++++++++ src/ngx_stream_lua_proxy_ssl_certby.h | 38 + src/ngx_stream_lua_ssl.h | 1 + src/ngx_stream_lua_uthread.c | 1 + src/ngx_stream_lua_util.h | 3 + src/ngx_stream_lua_variable.c | 1 + t/165-proxy-ssl-cert-by.t | 1302 +++++++++++++++++++++++++ 14 files changed, 2399 insertions(+) create mode 100644 src/ngx_stream_lua_proxy_ssl_certby.c create mode 100644 src/ngx_stream_lua_proxy_ssl_certby.h create mode 100644 t/165-proxy-ssl-cert-by.t diff --git a/README.md b/README.md index 614a8b32..9b0153c6 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,8 @@ behavior. * [ssl_client_hello_by_lua_file](https://github.com/openresty/lua-nginx-module#ssl_client_hello_by_lua_file) * [ssl_certificate_by_lua_block](https://github.com/openresty/lua-nginx-module#ssl_certificate_by_lua_block) * [ssl_certificate_by_lua_file](https://github.com/openresty/lua-nginx-module#ssl_certificate_by_lua_file) +* [proxy_ssl_certificate_by_lua_block](https://github.com/openresty/lua-nginx-module#proxy_ssl_certificate_by_lua_block) +* [proxy_ssl_certificate_by_lua_file](https://github.com/openresty/lua-nginx-module#proxy_ssl_certificate_by_lua_file) * [proxy_ssl_verify_by_lua_block](https://github.com/openresty/lua-nginx-module#proxy_ssl_verify_by_lua_block) * [proxy_ssl_verify_by_lua_file](https://github.com/openresty/lua-nginx-module#proxy_ssl_verify_by_lua_file) * [lua_shared_dict](https://github.com/openresty/lua-nginx-module#lua_shared_dict) diff --git a/config b/config index a6214034..56c7b554 100644 --- a/config +++ b/config @@ -278,6 +278,7 @@ STREAM_LUA_SRCS=" \ $ngx_addon_dir/src/ngx_stream_lua_semaphore.c \ $ngx_addon_dir/src/ngx_stream_lua_ssl_client_helloby.c \ $ngx_addon_dir/src/ngx_stream_lua_ssl_certby.c \ + $ngx_addon_dir/src/ngx_stream_lua_proxy_ssl_certby.c \ $ngx_addon_dir/src/ngx_stream_lua_proxy_ssl_verifyby.c \ $ngx_addon_dir/src/ngx_stream_lua_log_ringbuf.c \ $ngx_addon_dir/src/ngx_stream_lua_input_filters.c \ @@ -323,6 +324,7 @@ STREAM_LUA_DEPS=" \ $ngx_addon_dir/src/ngx_stream_lua_semaphore.h \ $ngx_addon_dir/src/ngx_stream_lua_ssl_client_helloby.h \ $ngx_addon_dir/src/ngx_stream_lua_ssl_certby.h \ + $ngx_addon_dir/src/ngx_stream_lua_proxy_ssl_certby.h \ $ngx_addon_dir/src/ngx_stream_lua_proxy_ssl_verifyby.h \ $ngx_addon_dir/src/ngx_stream_lua_log_ringbuf.h \ $ngx_addon_dir/src/ngx_stream_lua_input_filters.h \ diff --git a/src/ngx_stream_lua_common.h b/src/ngx_stream_lua_common.h index 0a09b306..f09b1051 100644 --- a/src/ngx_stream_lua_common.h +++ b/src/ngx_stream_lua_common.h @@ -138,6 +138,7 @@ #ifdef HAVE_PROXY_SSL_PATCH #define NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY 0x0100 +#define NGX_STREAM_LUA_CONTEXT_PROXY_SSL_CERT 0x0200 #endif @@ -277,6 +278,10 @@ struct ngx_stream_lua_srv_conf_s { #ifdef HAVE_PROXY_SSL_PATCH struct { + ngx_stream_lua_srv_conf_handler_pt proxy_ssl_cert_handler; + ngx_str_t proxy_ssl_cert_src; + u_char *proxy_ssl_cert_src_key; + ngx_stream_lua_srv_conf_handler_pt proxy_ssl_verify_handler; ngx_str_t proxy_ssl_verify_src; u_char *proxy_ssl_verify_src_key; diff --git a/src/ngx_stream_lua_control.c b/src/ngx_stream_lua_control.c index 4794ed22..92bf2cf0 100644 --- a/src/ngx_stream_lua_control.c +++ b/src/ngx_stream_lua_control.c @@ -117,6 +117,7 @@ ngx_stream_lua_ffi_exit(ngx_stream_lua_request_t *r, int status, u_char *err, | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_STREAM_LUA_CONTEXT_SSL_CERT #ifdef HAVE_PROXY_SSL_PATCH + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_CERT | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY #endif | NGX_STREAM_LUA_CONTEXT_PREREAD, @@ -127,6 +128,7 @@ ngx_stream_lua_ffi_exit(ngx_stream_lua_request_t *r, int status, u_char *err, if (ctx->context & (NGX_STREAM_LUA_CONTEXT_SSL_CERT #ifdef HAVE_PROXY_SSL_PATCH + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_CERT | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY #endif | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO )) diff --git a/src/ngx_stream_lua_coroutine.c b/src/ngx_stream_lua_coroutine.c index e73526ba..1839b009 100644 --- a/src/ngx_stream_lua_coroutine.c +++ b/src/ngx_stream_lua_coroutine.c @@ -206,6 +206,7 @@ ngx_stream_lua_coroutine_resume(lua_State *L) | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_STREAM_LUA_CONTEXT_SSL_CERT #ifdef HAVE_PROXY_SSL_PATCH + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_CERT | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY #endif | NGX_STREAM_LUA_CONTEXT_PREREAD @@ -270,6 +271,7 @@ ngx_stream_lua_coroutine_yield(lua_State *L) | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_STREAM_LUA_CONTEXT_SSL_CERT #ifdef HAVE_PROXY_SSL_PATCH + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_CERT | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY #endif | NGX_STREAM_LUA_CONTEXT_PREREAD @@ -433,6 +435,7 @@ ngx_stream_lua_coroutine_status(lua_State *L) | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_STREAM_LUA_CONTEXT_SSL_CERT #ifdef HAVE_PROXY_SSL_PATCH + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_CERT | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY #endif | NGX_STREAM_LUA_CONTEXT_PREREAD diff --git a/src/ngx_stream_lua_module.c b/src/ngx_stream_lua_module.c index 3afa8df5..ba47a7cc 100644 --- a/src/ngx_stream_lua_module.c +++ b/src/ngx_stream_lua_module.c @@ -32,6 +32,7 @@ #include "ngx_stream_lua_ssl_certby.h" #ifdef HAVE_PROXY_SSL_PATCH +#include "ngx_stream_lua_proxy_ssl_certby.h" #include "ngx_stream_lua_proxy_ssl_verifyby.h" #endif @@ -428,6 +429,20 @@ static ngx_command_t ngx_stream_lua_cmds[] = { #ifdef HAVE_PROXY_SSL_PATCH /* same context as proxy_pass directive */ + { ngx_string("proxy_ssl_certificate_by_lua_block"), + NGX_STREAM_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_stream_lua_proxy_ssl_cert_by_lua_block, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + (void *) ngx_stream_lua_proxy_ssl_cert_handler_inline }, + + { ngx_string("proxy_ssl_certificate_by_lua_file"), + NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_lua_proxy_ssl_cert_by_lua, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + (void *) ngx_stream_lua_proxy_ssl_cert_handler_file }, + { ngx_string("proxy_ssl_verify_by_lua_block"), NGX_STREAM_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, ngx_stream_lua_proxy_ssl_verify_by_lua_block, @@ -855,6 +870,10 @@ ngx_stream_lua_create_srv_conf(ngx_conf_t *cf) * lscf->srv.ssl_client_hello_src = { 0, NULL }; * lscf->srv.ssl_client_hello_src_key = NULL; * + * lscf->ups.proxy_ssl_cert_handler = NULL; + * lscf->ups.proxy_ssl_cert_src = { 0, NULL }; + * lscf->ups.proxy_ssl_cert_src_key = NULL; + * * lscf->ups.proxy_ssl_verify_handler = NULL; * lscf->ups.proxy_ssl_verify_src = { 0, NULL }; * lscf->ups.proxy_ssl_verify_src_key = NULL; @@ -1038,6 +1057,18 @@ ngx_stream_lua_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) #endif #ifdef HAVE_PROXY_SSL_PATCH + if (conf->ups.proxy_ssl_cert_src.len == 0) { + conf->ups.proxy_ssl_cert_src = prev->ups.proxy_ssl_cert_src; + conf->ups.proxy_ssl_cert_handler = prev->ups.proxy_ssl_cert_handler; + conf->ups.proxy_ssl_cert_src_key = prev->ups.proxy_ssl_cert_src_key; + } + + if (conf->ups.proxy_ssl_cert_src.len) { + if (ngx_stream_lua_proxy_ssl_cert_set_callback(cf) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + if (conf->ups.proxy_ssl_verify_src.len == 0) { conf->ups.proxy_ssl_verify_src = prev->ups.proxy_ssl_verify_src; conf->ups.proxy_ssl_verify_handler = prev->ups.proxy_ssl_verify_handler; diff --git a/src/ngx_stream_lua_phase.c b/src/ngx_stream_lua_phase.c index 8e647947..6db1a7dc 100644 --- a/src/ngx_stream_lua_phase.c +++ b/src/ngx_stream_lua_phase.c @@ -67,6 +67,10 @@ ngx_stream_lua_ngx_get_phase(lua_State *L) break; #ifdef HAVE_PROXY_SSL_PATCH + case NGX_STREAM_LUA_CONTEXT_PROXY_SSL_CERT: + lua_pushliteral(L, "proxy_ssl_cert"); + break; + case NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY: lua_pushliteral(L, "proxy_ssl_verify"); break; diff --git a/src/ngx_stream_lua_proxy_ssl_certby.c b/src/ngx_stream_lua_proxy_ssl_certby.c new file mode 100644 index 00000000..e7e5a89f --- /dev/null +++ b/src/ngx_stream_lua_proxy_ssl_certby.c @@ -0,0 +1,1004 @@ +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + +#ifndef DDEBUG +#define DDEBUG 0 +#endif +#include "ddebug.h" + + +#if (NGX_STREAM_SSL) + +#include "ngx_stream_lua_cache.h" +#include "ngx_stream_lua_initworkerby.h" +#include "ngx_stream_lua_util.h" +#include "ngx_stream_ssl_module.h" +#include "ngx_stream_lua_contentby.h" +#include "ngx_stream_lua_directive.h" +#include "ngx_stream_lua_ssl.h" + +#ifdef HAVE_PROXY_SSL_PATCH +#include "ngx_stream_lua_proxy_ssl_certby.h" + + +static void ngx_stream_lua_proxy_ssl_cert_done(void *data); +static void ngx_stream_lua_proxy_ssl_cert_aborted(void *data); +static ngx_int_t ngx_stream_lua_proxy_ssl_cert_by_chunk(lua_State *L, + ngx_stream_lua_request_t *r); + + +ngx_int_t +ngx_stream_lua_proxy_ssl_cert_set_callback(ngx_conf_t *cf) +{ + +#ifdef LIBRESSL_VERSION_NUMBER + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "LibreSSL does not support by proxy_ssl_certificate_by_lua*"); + + return NGX_ERROR; + +#else + + ngx_flag_t proxy_ssl = 0; + ngx_pool_cleanup_t *cln; + ngx_ssl_t *ssl; + void *pscf; + + /* + * Nginx doesn't export ngx_stream_proxy_srv_conf_t, so we can't directly + * get pscf here, and we also don't want to change ngx_stream_proxy_module's + * code organization, since that it means to add a header file to Nginx. + * I know it's a bit clumsy here, anyway the solution is good enough + */ + for (cln = cf->pool->cleanup; cln; cln = cln->next) { + if (cln->handler != ngx_ssl_cleanup_ctx) { + continue; + } + + ssl = cln->data; + + pscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_proxy_module); + if (pscf == ngx_ssl_get_server_conf(ssl->ctx)) { + /* here we make sure that ssl is pscf->ssl */ + proxy_ssl = 1; + + break; + } + } + + if (!proxy_ssl) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "proxy_ssl_certificate_by_lua* should be used with " + "proxy_ssl directive"); + + return NGX_ERROR; + } + +#if OPENSSL_VERSION_NUMBER >= 0x1000205fL + + SSL_CTX_set_cert_cb(ssl->ctx, ngx_stream_lua_proxy_ssl_cert_handler, NULL); + + return NGX_OK; + +#else + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "OpenSSL too old to support " + "proxy_ssl_certificate_by_lua*"); + + return NGX_ERROR; + +#endif + +#endif +} + + +ngx_int_t +ngx_stream_lua_proxy_ssl_cert_handler_file(ngx_stream_lua_request_t *r, + ngx_stream_lua_srv_conf_t *lscf, lua_State *L) +{ + ngx_int_t rc; + + rc = ngx_stream_lua_cache_loadfile(r->connection->log, L, + lscf->ups.proxy_ssl_cert_src.data, + lscf->ups.proxy_ssl_cert_src_key); + if (rc != NGX_OK) { + return rc; + } + + /* make sure we have a valid code chunk */ + ngx_stream_lua_assert(lua_isfunction(L, -1)); + + return ngx_stream_lua_proxy_ssl_cert_by_chunk(L, r); +} + + +ngx_int_t +ngx_stream_lua_proxy_ssl_cert_handler_inline(ngx_stream_lua_request_t *r, + ngx_stream_lua_srv_conf_t *lscf, lua_State *L) +{ + ngx_int_t rc; + + rc = ngx_stream_lua_cache_loadbuffer(r->connection->log, L, + lscf->ups.proxy_ssl_cert_src.data, + lscf->ups.proxy_ssl_cert_src.len, + lscf->ups.proxy_ssl_cert_src_key, + "=proxy_ssl_certificate_by_lua"); + if (rc != NGX_OK) { + return rc; + } + + /* make sure we have a valid code chunk */ + ngx_stream_lua_assert(lua_isfunction(L, -1)); + + return ngx_stream_lua_proxy_ssl_cert_by_chunk(L, r); +} + + +char * +ngx_stream_lua_proxy_ssl_cert_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + char *rv; + ngx_conf_t save; + + save = *cf; + cf->handler = ngx_stream_lua_proxy_ssl_cert_by_lua; + cf->handler_conf = conf; + + rv = ngx_stream_lua_conf_lua_block_parse(cf, cmd); + + *cf = save; + + return rv; +} + + +char * +ngx_stream_lua_proxy_ssl_cert_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ +#if OPENSSL_VERSION_NUMBER < 0x1000205fL + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "at least OpenSSL 1.0.2e required but found " + OPENSSL_VERSION_TEXT); + + return NGX_CONF_ERROR; + +#else + + u_char *p; + u_char *name; + ngx_str_t *value; + ngx_stream_lua_srv_conf_t *lscf = conf; + + /* must specify a concrete handler */ + if (cmd->post == NULL) { + return NGX_CONF_ERROR; + } + + if (lscf->ups.proxy_ssl_cert_handler) { + return "is duplicate"; + } + + if (ngx_stream_lua_ssl_init(cf->log) != NGX_OK) { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + lscf->ups.proxy_ssl_cert_handler = + (ngx_stream_lua_srv_conf_handler_pt) cmd->post; + + if (cmd->post == ngx_stream_lua_proxy_ssl_cert_handler_file) { + /* Lua code in an external file */ + + name = ngx_stream_lua_rebase_path(cf->pool, value[1].data, + value[1].len); + if (name == NULL) { + return NGX_CONF_ERROR; + } + + lscf->ups.proxy_ssl_cert_src.data = name; + lscf->ups.proxy_ssl_cert_src.len = ngx_strlen(name); + + p = ngx_palloc(cf->pool, NGX_STREAM_LUA_FILE_KEY_LEN + 1); + if (p == NULL) { + return NGX_CONF_ERROR; + } + + lscf->ups.proxy_ssl_cert_src_key = p; + + p = ngx_copy(p, NGX_STREAM_LUA_FILE_TAG, NGX_STREAM_LUA_FILE_TAG_LEN); + p = ngx_stream_lua_digest_hex(p, value[1].data, value[1].len); + *p = '\0'; + + } else { + /* inlined Lua code */ + + lscf->ups.proxy_ssl_cert_src = value[1]; + + p = ngx_palloc(cf->pool, + sizeof("proxy_ssl_certificate_by_lua") + + NGX_STREAM_LUA_INLINE_KEY_LEN); + if (p == NULL) { + return NGX_CONF_ERROR; + } + + lscf->ups.proxy_ssl_cert_src_key = p; + + p = ngx_copy(p, "proxy_ssl_certificate_by_lua", + sizeof("proxy_ssl_certificate_by_lua") - 1); + p = ngx_copy(p, NGX_STREAM_LUA_INLINE_TAG, + NGX_STREAM_LUA_INLINE_TAG_LEN); + p = ngx_stream_lua_digest_hex(p, value[1].data, value[1].len); + *p = '\0'; + } + + return NGX_CONF_OK; + +#endif /* OPENSSL_VERSION_NUMBER < 0x1000205fL */ +} + + +int +ngx_stream_lua_proxy_ssl_cert_handler(ngx_ssl_conn_t *ssl_conn, void *data) +{ + lua_State *L; + ngx_int_t rc; + ngx_connection_t *c; + ngx_stream_lua_request_t *r = NULL; + ngx_pool_cleanup_t *cln; + ngx_stream_lua_srv_conf_t *lscf; + ngx_stream_lua_ssl_ctx_t *cctx; + ngx_stream_session_t *s; + + c = ngx_ssl_get_connection(ssl_conn); /* upstream connection */ + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "proxy ssl cert: connection reusable: %ud", c->reusable); + + cctx = ngx_stream_lua_ssl_get_ctx(c->ssl->connection); + + dd("proxy ssl cert handler, cert-ctx=%p", cctx); + + if (cctx && cctx->entered_proxy_ssl_cert_handler) { + /* not the first time */ + + if (cctx->done) { + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "proxy_ssl_certificate_by_lua: " + "cert cb exit code: %d", + cctx->exit_code); + + dd("lua proxy ssl cert done, finally"); + return cctx->exit_code; + } + + return -1; + } + + dd("first time"); + + ngx_reusable_connection(c, 0); + + s = c->data; + + r = ngx_stream_lua_create_fake_request(s); + if (r == NULL) { + goto failed; + } + + if (cctx == NULL) { + cctx = ngx_pcalloc(c->pool, sizeof(ngx_stream_lua_ssl_ctx_t)); + if (cctx == NULL) { + goto failed; /* error */ + } + + cctx->ctx_ref = LUA_NOREF; + } + + cctx->connection = c; + cctx->request = r; + cctx->exit_code = 1; /* successful by default */ + cctx->done = 0; + cctx->entered_proxy_ssl_cert_handler = 1; + cctx->pool = ngx_create_pool(128, c->log); + if (cctx->pool == NULL) { + goto failed; + } + + dd("setting cctx"); + + if (SSL_set_ex_data(c->ssl->connection, ngx_stream_lua_ssl_ctx_index, + cctx) == 0) + { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_ex_data() failed"); + goto failed; + } + + lscf = ngx_stream_lua_get_module_srv_conf(r, ngx_stream_lua_module); + + /* TODO honor lua_code_cache off */ + L = ngx_stream_lua_get_lua_vm(r, NULL); + + c->log->action = "loading proxy ssl certificate by lua"; + + rc = lscf->ups.proxy_ssl_cert_handler(r, lscf, L); + + if (rc >= NGX_OK || rc == NGX_ERROR) { + cctx->done = 1; + + if (cctx->cleanup) { + *cctx->cleanup = NULL; + } + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, c->log, 0, + "proxy_ssl_certificate_by_lua: " + "handler return value: %i, cert cb exit code: %d", + rc, cctx->exit_code); + + c->log->action = "proxy pass SSL handshaking"; + return cctx->exit_code; + } + + /* rc == NGX_DONE */ + + cln = ngx_pool_cleanup_add(cctx->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->handler = ngx_stream_lua_proxy_ssl_cert_done; + cln->data = cctx; + + if (cctx->cleanup == NULL) { + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->data = cctx; + cctx->cleanup = &cln->handler; + } + + *cctx->cleanup = ngx_stream_lua_proxy_ssl_cert_aborted; + + return -1; + +#if 1 +failed: + + if (cctx && cctx->pool) { + ngx_destroy_pool(cctx->pool); + } + + return 0; /* failure or error */ +#endif +} + + +static void +ngx_stream_lua_proxy_ssl_cert_done(void *data) +{ + ngx_connection_t *c; + ngx_stream_lua_ssl_ctx_t *cctx = data; + + dd("lua proxy ssl cert done"); + + if (cctx->aborted) { + return; + } + + ngx_stream_lua_assert(cctx->done == 0); + + cctx->done = 1; + + if (cctx->cleanup) { + *cctx->cleanup = NULL; + } + + c = cctx->connection; + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + c->log->action = "proxy pass SSL handshaking"; + + ngx_post_event(c->write, &ngx_posted_events); +} + + +static void +ngx_stream_lua_proxy_ssl_cert_aborted(void *data) +{ + ngx_stream_lua_ssl_ctx_t *cctx = data; + + dd("lua proxy ssl cert aborted"); + + if (cctx->done) { + /* completed successfully already */ + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, cctx->connection->log, 0, + "proxy_ssl_certificate_by_lua: cert cb aborted"); + + cctx->aborted = 1; + cctx->connection->ssl = NULL; + cctx->exit_code = 0; + if (cctx->pool) { + ngx_destroy_pool(cctx->pool); + cctx->pool = NULL; + } +} + + +static ngx_int_t +ngx_stream_lua_proxy_ssl_cert_by_chunk(lua_State *L, + ngx_stream_lua_request_t *r) +{ + int co_ref; + ngx_int_t rc; + lua_State *co; + ngx_stream_lua_ctx_t *ctx; + ngx_pool_cleanup_t *cln; + ngx_stream_upstream_t *u; + ngx_connection_t *c; + ngx_stream_lua_ssl_ctx_t *cctx; + + ctx = ngx_stream_lua_get_module_ctx(r, ngx_stream_lua_module); + + if (ctx == NULL) { + ctx = ngx_stream_lua_create_ctx(r->session); + if (ctx == NULL) { + rc = NGX_ERROR; + ngx_stream_lua_finalize_request(r, rc); + return rc; + } + + } else { + dd("reset ctx"); + ngx_stream_lua_reset_ctx(r, L, ctx); + } + + ctx->entered_content_phase = 1; + + /* {{{ new coroutine to handle request */ + co = ngx_stream_lua_new_thread(r, L, &co_ref); + + if (co == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "stream failed to create new" + " coroutine to handle request"); + + rc = NGX_ERROR; + ngx_stream_lua_finalize_request(r, rc); + return rc; + } + + /* move code closure to new coroutine */ + lua_xmove(L, co, 1); + +#ifndef OPENRESTY_LUAJIT + /* set closure's env table to new coroutine's globals table */ + ngx_stream_lua_get_globals_table(co); + lua_setfenv(co, -2); +#endif + + /* save nginx request in coroutine globals table */ + ngx_stream_lua_set_req(co, r); + + ctx->cur_co_ctx = &ctx->entry_co_ctx; + ctx->cur_co_ctx->co = co; + ctx->cur_co_ctx->co_ref = co_ref; +#ifdef NGX_LUA_USE_ASSERT + ctx->cur_co_ctx->co_top = 1; +#endif + + ngx_stream_lua_attach_co_ctx_to_L(co, ctx->cur_co_ctx); + + /* register request cleanup hooks */ + if (ctx->cleanup == NULL) { + u = r->session->upstream; + c = u->peer.connection; + cctx = ngx_stream_lua_ssl_get_ctx(c->ssl->connection); + + cln = ngx_pool_cleanup_add(cctx->pool, 0); + if (cln == NULL) { + rc = NGX_ERROR; + ngx_stream_lua_finalize_request(r, rc); + return rc; + } + + cln->handler = ngx_stream_lua_request_cleanup_handler; + cln->data = ctx; + ctx->cleanup = &cln->handler; + } + + ctx->context = NGX_STREAM_LUA_CONTEXT_PROXY_SSL_CERT; + + rc = ngx_stream_lua_run_thread(L, r, ctx, 0); + + if (rc == NGX_ERROR || rc >= NGX_OK) { + /* do nothing */ + + } else if (rc == NGX_AGAIN) { + rc = ngx_stream_lua_content_run_posted_threads(L, r, ctx, 0); + + } else if (rc == NGX_DONE) { + rc = ngx_stream_lua_content_run_posted_threads(L, r, ctx, 1); + + } else { + rc = NGX_OK; + } + + ngx_stream_lua_finalize_request(r, rc); + return rc; +} + + +int +ngx_stream_lua_ffi_proxy_ssl_get_tls1_version(ngx_stream_lua_request_t *r, + char **err) +{ + ngx_stream_upstream_t *u; + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + + u = r->session->upstream; + if (u == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + c = u->peer.connection; + if (c == NULL || c->ssl == NULL) { + *err = "bad upstream connection"; + return NGX_ERROR; + } + + ssl_conn = c->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + dd("tls1 ver: %d", SSL_version(ssl_conn)); + + return SSL_version(ssl_conn); +} + + +int +ngx_stream_lua_ffi_proxy_ssl_clear_certs(ngx_stream_lua_request_t *r, + char **err) +{ +#ifdef LIBRESSL_VERSION_NUMBER + + *err = "LibreSSL not supported"; + return NGX_ERROR; + +#else + +# if OPENSSL_VERSION_NUMBER < 0x1000205fL + + *err = "at least OpenSSL 1.0.2e required but found " OPENSSL_VERSION_TEXT; + return NGX_ERROR; + +# else + + ngx_stream_upstream_t *u; + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + + u = r->session->upstream; + if (u == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + c = u->peer.connection; + if (c == NULL || c->ssl == NULL) { + *err = "bad upstream connection"; + return NGX_ERROR; + } + + ssl_conn = c->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + SSL_certs_clear(ssl_conn); + return NGX_OK; + +# endif /* OPENSSL_VERSION_NUMBER < 0x1000205fL */ +#endif +} + + +int +ngx_stream_lua_ffi_proxy_ssl_set_der_certificate(ngx_stream_lua_request_t *r, + const char *data, size_t len, char **err) +{ +#ifdef LIBRESSL_VERSION_NUMBER + + *err = "LibreSSL not supported"; + return NGX_ERROR; + +#else + +# if OPENSSL_VERSION_NUMBER < 0x1000205fL + + *err = "at least OpenSSL 1.0.2e required but found " OPENSSL_VERSION_TEXT; + return NGX_ERROR; + +# else + + ngx_stream_upstream_t *u; + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + BIO *bio = NULL; + X509 *x509 = NULL; + + u = r->session->upstream; + if (u == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + c = u->peer.connection; + if (c == NULL || c->ssl == NULL) { + *err = "bad upstream connection"; + return NGX_ERROR; + } + + ssl_conn = c->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + bio = BIO_new_mem_buf((char *) data, len); + if (bio == NULL) { + *err = "BIO_new_mem_buf() failed"; + goto failed; + } + + x509 = d2i_X509_bio(bio, NULL); + if (x509 == NULL) { + *err = "d2i_X509_bio() failed"; + goto failed; + } + + if (SSL_use_certificate(ssl_conn, x509) == 0) { + *err = "SSL_use_certificate() failed"; + goto failed; + } + + X509_free(x509); + x509 = NULL; + + /* read rest of the chain */ + + while (!BIO_eof(bio)) { + + x509 = d2i_X509_bio(bio, NULL); + if (x509 == NULL) { + *err = "d2i_X509_bio() failed"; + goto failed; + } + + if (SSL_add0_chain_cert(ssl_conn, x509) == 0) { + *err = "SSL_add0_chain_cert() failed"; + goto failed; + } + } + + BIO_free(bio); + + *err = NULL; + return NGX_OK; + +failed: + + if (bio) { + BIO_free(bio); + } + + if (x509) { + X509_free(x509); + } + + ERR_clear_error(); + + return NGX_ERROR; + +# endif /* OPENSSL_VERSION_NUMBER < 0x1000205fL */ +#endif +} + + +int +ngx_stream_lua_ffi_proxy_ssl_set_der_private_key(ngx_stream_lua_request_t *r, + const char *data, size_t len, char **err) +{ + ngx_stream_upstream_t *u; + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + BIO *bio = NULL; + EVP_PKEY *pkey = NULL; + + u = r->session->upstream; + if (u == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + c = u->peer.connection; + if (c == NULL || c->ssl == NULL) { + *err = "bad upstream connection"; + return NGX_ERROR; + } + + ssl_conn = c->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + bio = BIO_new_mem_buf((char *) data, len); + if (bio == NULL) { + *err = "BIO_new_mem_buf() failed"; + goto failed; + } + + pkey = d2i_PrivateKey_bio(bio, NULL); + if (pkey == NULL) { + *err = "d2i_PrivateKey_bio() failed"; + goto failed; + } + + if (SSL_use_PrivateKey(ssl_conn, pkey) == 0) { + *err = "SSL_use_PrivateKey() failed"; + goto failed; + } + + EVP_PKEY_free(pkey); + BIO_free(bio); + + return NGX_OK; + +failed: + + if (pkey) { + EVP_PKEY_free(pkey); + } + + if (bio) { + BIO_free(bio); + } + + ERR_clear_error(); + + return NGX_ERROR; +} + + +int +ngx_stream_lua_ffi_proxy_ssl_set_cert(ngx_stream_lua_request_t *r, + void *cdata, char **err) +{ +#ifdef LIBRESSL_VERSION_NUMBER + + *err = "LibreSSL not supported"; + return NGX_ERROR; + +#else + +# if OPENSSL_VERSION_NUMBER < 0x1000205fL + + *err = "at least OpenSSL 1.0.2e required but found " OPENSSL_VERSION_TEXT; + return NGX_ERROR; + +# else + +#ifdef OPENSSL_IS_BORINGSSL + size_t i; +#else + int i; +#endif + ngx_stream_upstream_t *u; + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + X509 *x509 = NULL; + STACK_OF(X509) *chain = cdata; + + u = r->session->upstream; + if (u == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + c = u->peer.connection; + if (c == NULL || c->ssl == NULL) { + *err = "bad upstream connection"; + return NGX_ERROR; + } + + ssl_conn = c->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + if (sk_X509_num(chain) < 1) { + *err = "invalid certificate chain"; + goto failed; + } + + x509 = sk_X509_value(chain, 0); + if (x509 == NULL) { + *err = "sk_X509_value() failed"; + goto failed; + } + + if (SSL_use_certificate(ssl_conn, x509) == 0) { + *err = "SSL_use_certificate() failed"; + goto failed; + } + + x509 = NULL; + + /* read rest of the chain */ + + for (i = 1; i < sk_X509_num(chain); i++) { + + x509 = sk_X509_value(chain, i); + if (x509 == NULL) { + *err = "sk_X509_value() failed"; + goto failed; + } + + if (SSL_add1_chain_cert(ssl_conn, x509) == 0) { + *err = "SSL_add1_chain_cert() failed"; + goto failed; + } + } + + *err = NULL; + return NGX_OK; + +failed: + + ERR_clear_error(); + + return NGX_ERROR; + +# endif /* OPENSSL_VERSION_NUMBER < 0x1000205fL */ +#endif +} + + +int +ngx_stream_lua_ffi_proxy_ssl_set_priv_key(ngx_stream_lua_request_t *r, + void *cdata, char **err) +{ + ngx_stream_upstream_t *u; + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + EVP_PKEY *pkey = NULL; + + u = r->session->upstream; + if (u == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + c = u->peer.connection; + if (c == NULL || c->ssl == NULL) { + *err = "bad upstream connection"; + return NGX_ERROR; + } + + ssl_conn = c->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + pkey = cdata; + if (pkey == NULL) { + *err = "invalid private key failed"; + goto failed; + } + + if (SSL_use_PrivateKey(ssl_conn, pkey) == 0) { + *err = "SSL_use_PrivateKey() failed"; + goto failed; + } + + return NGX_OK; + +failed: + + ERR_clear_error(); + + return NGX_ERROR; +} + + +#else /* HAVE_PROXY_SSL_PATCH */ + + +int +ngx_stream_lua_ffi_proxy_ssl_get_tls1_version(ngx_stream_lua_request_t *r, + char **err) +{ + *err = "Does not have HAVE_PROXY_SSL_PATCH to support this function"; + + return NGX_ERROR; +} + + +int +ngx_stream_lua_ffi_proxy_ssl_clear_certs(ngx_stream_lua_request_t *r, + char **err) +{ + *err = "Does not have HAVE_PROXY_SSL_PATCH to support this function"; + + return NGX_ERROR; +} + + +int +ngx_stream_lua_ffi_proxy_ssl_set_der_certificate(ngx_stream_lua_request_t *r, + const char *data, size_t len, char **err) +{ + *err = "Does not have HAVE_PROXY_SSL_PATCH to support this function"; + + return NGX_ERROR; +} + + +int +ngx_stream_lua_ffi_proxy_ssl_set_der_private_key(ngx_stream_lua_request_t *r, + const char *data, size_t len, char **err) +{ + *err = "Does not have HAVE_PROXY_SSL_PATCH to support this function"; + + return NGX_ERROR; +} + + +int +ngx_stream_lua_ffi_proxy_ssl_set_cert(ngx_stream_lua_request_t *r, + void *cdata, char **err) +{ + *err = "Does not have HAVE_PROXY_SSL_PATCH to support this function"; + + return NGX_ERROR; +} + + +int +ngx_stream_lua_ffi_proxy_ssl_set_priv_key(ngx_stream_lua_request_t *r, + void *cdata, char **err) +{ + *err = "Does not have HAVE_PROXY_SSL_PATCH to support this function"; + + return NGX_ERROR; +} + + +#endif /* HAVE_PROXY_SSL_PATCH */ +#endif /* NGX_STREAM_SSL */ diff --git a/src/ngx_stream_lua_proxy_ssl_certby.h b/src/ngx_stream_lua_proxy_ssl_certby.h new file mode 100644 index 00000000..b1d82592 --- /dev/null +++ b/src/ngx_stream_lua_proxy_ssl_certby.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + +#ifndef _NGX_STREAM_LUA_PROXY_SSL_CERTBY_H_INCLUDED_ +#define _NGX_STREAM_LUA_PROXY_SSL_CERTBY_H_INCLUDED_ + + +#include "ngx_stream_lua_common.h" + + +#if (NGX_STREAM_SSL) +#ifdef HAVE_PROXY_SSL_PATCH + +/* do not introduce ngx_stream_proxy_module to pollute ngx_stream_lua_module.c */ +extern ngx_module_t ngx_stream_proxy_module; + +ngx_int_t ngx_stream_lua_proxy_ssl_cert_handler_inline( + ngx_stream_lua_request_t *r, ngx_stream_lua_srv_conf_t *lscf, lua_State *L); + +ngx_int_t ngx_stream_lua_proxy_ssl_cert_handler_file( + ngx_stream_lua_request_t *r, ngx_stream_lua_srv_conf_t *lscf, lua_State *L); + +char *ngx_stream_lua_proxy_ssl_cert_by_lua_block(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); + +char *ngx_stream_lua_proxy_ssl_cert_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +int ngx_stream_lua_proxy_ssl_cert_handler(ngx_ssl_conn_t *ssl_conn, void *data); + +ngx_int_t ngx_stream_lua_proxy_ssl_cert_set_callback(ngx_conf_t *cf); + +#endif /* HAVE_PROXY_SSL_PATCH */ +#endif /* NGX_STREAM_SSL */ +#endif /* _NGX_STREAM_LUA_PROXY_SSL_CERTBY_H_INCLUDED_ */ + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_stream_lua_ssl.h b/src/ngx_stream_lua_ssl.h index 91d06f32..c3d475d2 100644 --- a/src/ngx_stream_lua_ssl.h +++ b/src/ngx_stream_lua_ssl.h @@ -53,6 +53,7 @@ typedef struct { unsigned entered_cert_handler:1; unsigned entered_sess_fetch_handler:1; #ifdef HAVE_PROXY_SSL_PATCH + unsigned entered_proxy_ssl_cert_handler:1; unsigned entered_proxy_ssl_verify_handler:1; #endif } ngx_stream_lua_ssl_ctx_t; diff --git a/src/ngx_stream_lua_uthread.c b/src/ngx_stream_lua_uthread.c index 8e33a015..06107d2c 100644 --- a/src/ngx_stream_lua_uthread.c +++ b/src/ngx_stream_lua_uthread.c @@ -236,6 +236,7 @@ ngx_stream_lua_uthread_kill(lua_State *L) | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_STREAM_LUA_CONTEXT_SSL_CERT #ifdef HAVE_PROXY_SSL_PATCH + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_CERT | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY #endif | NGX_STREAM_LUA_CONTEXT_TIMER); diff --git a/src/ngx_stream_lua_util.h b/src/ngx_stream_lua_util.h index 9839d99a..5cbaa6aa 100644 --- a/src/ngx_stream_lua_util.h +++ b/src/ngx_stream_lua_util.h @@ -79,6 +79,7 @@ extern char ngx_stream_lua_headers_metatable_key; | NGX_STREAM_LUA_CONTEXT_TIMER \ | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO \ | NGX_STREAM_LUA_CONTEXT_SSL_CERT \ + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_CERT \ | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY) @@ -92,6 +93,8 @@ extern char ngx_stream_lua_headers_metatable_key; : (c) == NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO ? \ "ssl_client_hello_by_lua*" \ : (c) == NGX_STREAM_LUA_CONTEXT_SSL_CERT ? "ssl_certificate_by_lua*" \ + : (c) == NGX_STREAM_LUA_CONTEXT_PROXY_SSL_CERT ? \ + "proxy_ssl_certificate_by_lua*" \ : (c) == NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY ? \ "proxy_ssl_verify_by_lua*" \ : "(unknown)") diff --git a/src/ngx_stream_lua_variable.c b/src/ngx_stream_lua_variable.c index 6caae5ab..a101abad 100644 --- a/src/ngx_stream_lua_variable.c +++ b/src/ngx_stream_lua_variable.c @@ -50,6 +50,7 @@ ngx_stream_lua_ffi_var_get(ngx_stream_lua_request_t *r, u_char *name_data, ctx = ngx_stream_lua_get_module_ctx(r, ngx_stream_lua_module); if (ctx->context & (NGX_STREAM_LUA_CONTEXT_SSL_CERT #ifdef HAVE_PROXY_SSL_PATCH + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_CERT | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY #endif | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO)) diff --git a/t/165-proxy-ssl-cert-by.t b/t/165-proxy-ssl-cert-by.t new file mode 100644 index 00000000..d2eb0f51 --- /dev/null +++ b/t/165-proxy-ssl-cert-by.t @@ -0,0 +1,1302 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua::Stream; +repeat_each(3); + +# All these tests need to have new openssl +my $NginxBinary = $ENV{'TEST_NGINX_BINARY'} || 'nginx'; +my $openssl_version = eval { `$NginxBinary -V 2>&1` }; + +if ($openssl_version =~ m/built with OpenSSL (0\S*|1\.0\S*|1\.1\.0\S*)/) { + plan(skip_all => "too old OpenSSL, need 1.1.1, was $1"); +} else { + plan tests => repeat_each() * (blocks() * 6 + 5); +} + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); +$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; + +#log_level 'warn'; +log_level 'debug'; + +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ + +=== TEST 1: without proxy_ssl on +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + + proxy_ssl_certificate_by_lua_block { + ngx.log(ngx.INFO, "hello world") + } +--- error_log +proxy_ssl_certificate_by_lua* should be used with proxy_ssl directive +--- no_error_log +[error] +[alert] +--- must_die + + + +=== TEST 2: proxy_ssl_certificate_by_lua in stream {} block +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + return 'it works!\n'; + } + + proxy_ssl_certificate_by_lua_block { + ngx.log(ngx.INFO, "hello world") + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; +--- error_log +"proxy_ssl_certificate_by_lua_block" directive is not allowed here +--- no_error_log +[error] +[alert] +--- must_die + + + +=== TEST 3: simple logging +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + ngx.log(ngx.INFO, "proxy_ssl_certificate_by_lua is running!") + } +--- stream_response +it works! +--- error_log +proxy_ssl_certificate_by_lua is running! +proxy_ssl_certificate_by_lua: handler return value: 0, cert cb exit code: 1 +--- no_error_log +[error] +[alert] + + + +=== TEST 4: sleep +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + local begin = ngx.now() + ngx.sleep(0.1) + print("elapsed in proxy ssl certificate by lua: ", ngx.now() - begin) + } +--- stream_response +it works! +--- error_log eval +qr/elapsed in proxy ssl certificate by lua: 0.(?:09|1\d)\d+ while loading proxy ssl certificate by lua,/, +--- no_error_log +[error] +[alert] + + + +=== TEST 5: timer +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + local function f() + print("my timer run!") + end + local ok, err = ngx.timer.at(0, f) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + return + end + } +--- stream_response +it works! +--- error_log +my timer run! +proxy_ssl_certificate_by_lua: handler return value: 0, cert cb exit code: 1 +--- no_error_log +[error] +[alert] + + + +=== TEST 6: ngx.exit(0) - no yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + ngx.exit(0) + ngx.log(ngx.ERR, "should never reached here...") + } +--- stream_response +it works! +--- error_log +lua exit with code 0 +--- no_error_log +should never reached here +[error] +[alert] +[emerg] + + + +=== TEST 7: ngx.exit(ngx.ERROR) - no yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_certificate_by_lua_block { + ngx.exit(ngx.ERROR) + ngx.log(ngx.ERR, "should never reached here...") + } +--- error_log eval +[ +'lua exit with code -1', +'proxy_ssl_certificate_by_lua: handler return value: -1, cert cb exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?callback failed/, +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 8: ngx.exit(0) - yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + ngx.sleep(0.001) + ngx.exit(0) + + ngx.log(ngx.ERR, "should never reached here...") + } +--- stream_response +it works! +--- error_log +lua exit with code 0 +--- no_error_log +should never reached here +[error] +[alert] +[emerg] + + + +=== TEST 9: ngx.exit(ngx.ERROR) - yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_certificate_by_lua_block { + ngx.sleep(0.001) + ngx.exit(ngx.ERROR) + + ngx.log(ngx.ERR, "should never reached here...") + } +--- error_log eval +[ +'lua exit with code -1', +'proxy_ssl_certificate_by_lua: cert cb exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?callback failed/, +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 10: lua exception - no yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_certificate_by_lua_block { + error("bad bad bad") + ngx.log(ngx.ERR, "should never reached here...") + } +--- error_log eval +[ +'runtime error: proxy_ssl_certificate_by_lua:2: bad bad bad', +'proxy_ssl_certificate_by_lua: handler return value: 500, cert cb exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?callback failed/, +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 11: lua exception - yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_certificate_by_lua_block { + ngx.sleep(0.001) + error("bad bad bad") + ngx.log(ngx.ERR, "should never reached here...") + } +--- error_log eval +[ +'runtime error: proxy_ssl_certificate_by_lua:3: bad bad bad', +'proxy_ssl_certificate_by_lua: cert cb exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?callback failed/, +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 12: get phase +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + print("get_phase: ", ngx.get_phase()) + } +--- stream_response +it works! +--- error_log +get_phase: proxy_ssl_cert +--- no_error_log +[error] +[alert] + + + +=== TEST 13: simple logging (by_lua_file) +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_certificate_by_lua_file html/a.lua; +--- stream_response +it works! +--- user_files +>>> a.lua +print("proxy ssl certificate by lua is running!") + +--- error_log +a.lua:1: proxy ssl certificate by lua is running! +--- no_error_log +[error] +[alert] + + + +=== TEST 14: coroutine API +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield + + local function f() + local cnt = 0 + for i = 1, 20 do + print("co yield: ", cnt) + cy() + cnt = cnt + 1 + end + end + + local c = cc(f) + for i = 1, 3 do + print("co resume, status: ", coroutine.status(c)) + cr(c) + end + } +--- stream_response +it works! +--- grep_error_log eval: qr/co (?:yield: \d+|resume, status: \w+)/ +--- grep_error_log_out +co resume, status: suspended +co yield: 0 +co resume, status: suspended +co yield: 1 +co resume, status: suspended +co yield: 2 +--- no_error_log +[error] +[alert] + + + +=== TEST 15: simple user thread wait with yielding +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + local function f() + ngx.sleep(0.01) + print("uthread: hello in thread") + return "done" + end + + local t, err = ngx.thread.spawn(f) + if not t then + ngx.log(ngx.ERR, "uthread: failed to spawn thread: ", err) + return ngx.exit(ngx.ERROR) + end + + print("uthread: thread created: ", coroutine.status(t)) + + local ok, res = ngx.thread.wait(t) + if not ok then + print("uthread: failed to wait thread: ", res) + return + end + + print("uthread: ", res) + } +--- stream_response +it works! +--- no_error_log +[error] +[alert] +--- grep_error_log eval: qr/uthread: [^.,]+/ +--- grep_error_log_out +uthread: thread created: running while loading proxy ssl certificate by lua +uthread: hello in thread while loading proxy ssl certificate by lua +uthread: done while loading proxy ssl certificate by lua + + + +=== TEST 16: uthread (kill) +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + local function f() + ngx.log(ngx.INFO, "uthread: hello from f()") + ngx.sleep(1) + end + + local t, err = ngx.thread.spawn(f) + if not t then + ngx.log(ngx.ERR, "failed to spawn thread: ", err) + return ngx.exit(ngx.ERROR) + end + + local ok, res = ngx.thread.kill(t) + if not ok then + ngx.log(ngx.ERR, "failed to kill thread: ", res) + return + end + + ngx.log(ngx.INFO, "uthread: killed") + + local ok, err = ngx.thread.kill(t) + if not ok then + ngx.log(ngx.INFO, "uthread: failed to kill: ", err) + end + } +--- stream_response +it works! +--- no_error_log +[error] +[alert] +[emerg] +--- grep_error_log eval: qr/uthread: [^.,]+/ +--- grep_error_log_out +uthread: hello from f() while loading proxy ssl certificate by lua +uthread: killed while loading proxy ssl certificate by lua +uthread: failed to kill: already waited or killed while loading proxy ssl certificate by lua + + + +=== TEST 17: ngx.exit(ngx.OK) - no yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + ngx.exit(ngx.OK) + ngx.log(ngx.ERR, "should never reached here...") + } +--- stream_response +it works! +--- error_log eval +[ +'proxy_ssl_certificate_by_lua: handler return value: 0, cert cb exit code: 1', +qr/\[debug\] .*? SSL_do_handshake: 1/, +'lua exit with code 0', +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 18: proxy_ssl_certificate_by_lua* without yield API (simple logic) +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + print("proxy ssl certificate: simple test start") + + -- Simple calculations without yield + local sum = 0 + for i = 1, 10 do + sum = sum + i + end + + print("proxy ssl certificate: calculated sum: ", sum) + + -- String operations + local str = "hello" + str = str .. " world" + print("proxy ssl certificate: concatenated string: ", str) + + -- Table operations + local t = {a = 1, b = 2, c = 3} + local count = 0 + for k, v in pairs(t) do + count = count + v + end + print("proxy ssl certificate: table sum: ", count) + + print("proxy ssl certificate: simple test done") + } +--- stream_response +it works! +--- grep_error_log eval: qr/(proxy ssl certificate: simple test start|proxy ssl certificate: calculated sum: 55|proxy ssl certificate: concatenated string: hello world|proxy ssl certificate: table sum: 6|proxy ssl certificate: simple test done)/ +--- grep_error_log_out +proxy ssl certificate: simple test start +proxy ssl certificate: calculated sum: 55 +proxy ssl certificate: concatenated string: hello world +proxy ssl certificate: table sum: 6 +proxy ssl certificate: simple test done + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 19: ngx.ctx to pass data from downstream phase to upstream phase +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + preread_by_lua_block { + ngx.ctx.greeting = "I am from preread phase" + } + + proxy_ssl_certificate_by_lua_block { + ngx.log(ngx.INFO, "greeting: ", ngx.ctx.greeting) + } +--- stream_response +it works! +--- error_log +greeting: I am from preread phase +proxy_ssl_certificate_by_lua: handler return value: 0, cert cb exit code: 1 +--- no_error_log +[error] +[alert] + + + +=== TEST 20: upstream connection aborted +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_connect_timeout 100ms; + + proxy_ssl_certificate_by_lua_block { + ngx.sleep(0.2) + } +--- error_log +proxy_ssl_certificate_by_lua: cert cb aborted +--- no_error_log +[error] +[alert] +--- wait: 0.5 + + + +=== TEST 21: cosocket +--- stream_config + server { + listen *:80; + + return "it works!\n"; + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + do + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + local ok, err = sock:connect("127.0.0.1", "80") + if not ok then + ngx.log(ngx.ERR, "failed to connect: ", err) + return + end + + ngx.log(ngx.INFO, "connected: ", ok) + + while true do + local line, err = sock:receive() + if not line then + -- ngx.log(ngx.ERR, "failed to receive response status line: ", err) + break + end + ngx.log(ngx.INFO, "received: ", line) + end + + local ok, err = sock:close() + ngx.log(ngx.INFO, "close: ", ok, " ", err) + end -- do + -- collectgarbage() + } +--- stream_response +it works! +--- error_log +connected: 1 +received: it works! +close: 1 nil +--- no_error_log +[error] +[alert] + + + +=== TEST 22: TLSv1.2, without proxy_ssl_certificate, lua does not set cert +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_protocols TLSv1.2; + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_protocols TLSv1.2; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + local proxy_ssl = require "ngx.proxyssl" + + local ver, err = proxy_ssl.get_tls1_version_str() + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.INFO, "got TLS1 version: ", ver) + } +--- stream_response +--- error_log +got TLS1 version: TLSv1.2 +client sent no required SSL certificate +--- no_error_log +[error] +[alert] + + + +=== TEST 23: TLSv1.2, without proxy_ssl_certificate, lua sets cert +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_protocols TLSv1.2; + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_protocols TLSv1.2; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local proxy_ssl = require "ngx.proxyssl" + local proxy_ssl_cert = require "ngx.ssl.proxysslcert" + + local ver, err = proxy_ssl.get_tls1_version_str() + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.INFO, "got TLS1 version: ", ver) + + local f = assert(io.open("t/cert/mtls_client.crt")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.parse_pem_cert(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to parse pem cert: ", err) + return + end + + local ok, err = proxy_ssl_cert.set_cert(cert) + if not ok then + ngx.log(ngx.ERR, "failed to set cert: ", err) + return + end + + local f = assert(io.open("t/cert/mtls_client.key")) + local pkey_data = f:read("*a") + f:close() + + local pkey, err = ssl.parse_pem_priv_key(pkey_data) + if not pkey then + ngx.log(ngx.ERR, "failed to parse pem key: ", err) + return + end + + local ok, err = proxy_ssl_cert.set_priv_key(pkey) + if not ok then + ngx.log(ngx.ERR, "failed to set private key: ", err) + return + end + } +--- stream_response +it works! +--- error_log +got TLS1 version: TLSv1.2 +--- no_error_log +[error] +[alert] + + + +=== TEST 24: TLSv1.3, without proxy_ssl_certificate, lua does not set cert +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_protocols TLSv1.3; + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_protocols TLSv1.3; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + local proxy_ssl = require "ngx.proxyssl" + + local ver, err = proxy_ssl.get_tls1_version_str() + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.INFO, "got TLS1 version: ", ver) + } +--- stream_response +--- error_log +got TLS1 version: TLSv1.3 +client sent no required SSL certificate +--- no_error_log +[error] +[alert] + + + +=== TEST 25: TLSv1.3, without proxy_ssl_certificate, lua sets cert +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_protocols TLSv1.3; + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_protocols TLSv1.3; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local proxy_ssl = require "ngx.proxyssl" + local proxy_ssl_cert = require "ngx.ssl.proxysslcert" + + local ver, err = proxy_ssl.get_tls1_version_str() + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.INFO, "got TLS1 version: ", ver) + + local f = assert(io.open("t/cert/mtls_client.crt")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.parse_pem_cert(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to parse pem cert: ", err) + return + end + + local ok, err = proxy_ssl_cert.set_cert(cert) + if not ok then + ngx.log(ngx.ERR, "failed to set cert: ", err) + return + end + + local f = assert(io.open("t/cert/mtls_client.key")) + local pkey_data = f:read("*a") + f:close() + + local pkey, err = ssl.parse_pem_priv_key(pkey_data) + if not pkey then + ngx.log(ngx.ERR, "failed to parse pem key: ", err) + return + end + + local ok, err = proxy_ssl_cert.set_priv_key(pkey) + if not ok then + ngx.log(ngx.ERR, "failed to set private key: ", err) + return + end + } +--- stream_response +it works! +--- error_log +got TLS1 version: TLSv1.3 +--- no_error_log +[error] +[alert] + + + +=== TEST 26: TLSv1.2, with proxy_ssl_certificate, lua does not set cert +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_protocols TLSv1.2; + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_protocols TLSv1.2; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + local proxy_ssl = require "ngx.proxyssl" + + local ver, err = proxy_ssl.get_tls1_version_str() + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.INFO, "got TLS1 version: ", ver) + } +--- stream_response +it works! +--- error_log +got TLS1 version: TLSv1.2 +--- no_error_log +[error] +[alert] + + + +=== TEST 27: TLSv1.3, with proxy_ssl_certificate, lua does not set cert +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_protocols TLSv1.3; + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_protocols TLSv1.3; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + local proxy_ssl = require "ngx.proxyssl" + + local ver, err = proxy_ssl.get_tls1_version_str() + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.INFO, "got TLS1 version: ", ver) + } +--- stream_response +it works! +--- error_log +got TLS1 version: TLSv1.3 +--- no_error_log +[error] +[alert] + + + +=== TEST 28: proxy_ssl_certificate_by_lua takes precedence over proxy_ssl_certificate +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_protocols TLSv1.3; + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_protocols TLSv1.3; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local proxy_ssl = require "ngx.proxyssl" + local proxy_ssl_cert = require "ngx.ssl.proxysslcert" + + local ver, err = proxy_ssl.get_tls1_version_str() + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.INFO, "got TLS1 version: ", ver) + + -- there exists proxy_ssl_certificate and proxy_ssl_certificate_key + -- directives in nginx conf, but here we use lua codes to clear them, + -- so that it can prove that proxy_ssl_certificate_by_lua takes + -- precedence over proxy_ssl_certificate related directives + proxy_ssl_cert.clear_certs() + } +--- stream_response +--- error_log +got TLS1 version: TLSv1.3 +client sent no required SSL certificate +--- no_error_log +[error] +[alert] From 46dc9e3cc07e4655b53d279af948a14437bfb2b2 Mon Sep 17 00:00:00 2001 From: willmafh Date: Sun, 19 Oct 2025 17:22:45 +0800 Subject: [PATCH 2/3] correct way to specify random listen port --- t/165-proxy-ssl-cert-by.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/165-proxy-ssl-cert-by.t b/t/165-proxy-ssl-cert-by.t index d2eb0f51..e6c7964f 100644 --- a/t/165-proxy-ssl-cert-by.t +++ b/t/165-proxy-ssl-cert-by.t @@ -863,7 +863,7 @@ proxy_ssl_certificate_by_lua: cert cb aborted === TEST 21: cosocket --- stream_config server { - listen *:80; + listen 127.0.0.1:$TEST_NGINX_RAND_PORT_1; return "it works!\n"; } @@ -893,7 +893,7 @@ proxy_ssl_certificate_by_lua: cert cb aborted local sock = ngx.socket.tcp() sock:settimeout(2000) - local ok, err = sock:connect("127.0.0.1", "80") + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_RAND_PORT_1) if not ok then ngx.log(ngx.ERR, "failed to connect: ", err) return From d10011dd8eb2aa1650fa8a260db6d09aa29f0d40 Mon Sep 17 00:00:00 2001 From: willmafh Date: Mon, 20 Oct 2025 14:50:27 +0800 Subject: [PATCH 3/3] tests: fixed CI --- t/165-proxy-ssl-cert-by.t | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/t/165-proxy-ssl-cert-by.t b/t/165-proxy-ssl-cert-by.t index e6c7964f..d8a56a6a 100644 --- a/t/165-proxy-ssl-cert-by.t +++ b/t/165-proxy-ssl-cert-by.t @@ -7,10 +7,10 @@ repeat_each(3); my $NginxBinary = $ENV{'TEST_NGINX_BINARY'} || 'nginx'; my $openssl_version = eval { `$NginxBinary -V 2>&1` }; -if ($openssl_version =~ m/built with OpenSSL (0\S*|1\.0\S*|1\.1\.0\S*)/) { - plan(skip_all => "too old OpenSSL, need 1.1.1, was $1"); +if ($openssl_version =~ m/built with OpenSSL (0|1\.0\.(?:0|1[^\d]|2[a-d]).*)/) { + plan(skip_all => "too old OpenSSL, need >= 1.0.2e, was $1"); } else { - plan tests => repeat_each() * (blocks() * 6 + 5); + plan tests => repeat_each() * (blocks() * 5 + 22); } $ENV{TEST_NGINX_HTML_DIR} ||= html_dir();