diff --git a/README.md b/README.md index 89af1c70..ea99e310 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_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) * [lua_socket_connect_timeout](https://github.com/openresty/lua-nginx-module#lua_socket_connect_timeout) * [lua_socket_buffer_size](https://github.com/openresty/lua-nginx-module#lua_socket_buffer_size) @@ -166,6 +168,7 @@ behavior. * [lua_ssl_verify_depth](https://github.com/openresty/lua-nginx-module#lua_ssl_verify_depth) * [lua_ssl_key_log](https://github.com/openresty/lua-nginx-module#lua_ssl_key_log) * [lua_ssl_conf_command](https://github.com/openresty/lua-nginx-module#lua_ssl_conf_command) +* [lua_upstream_skip_openssl_default_verify](https://github.com/openresty/lua-nginx-module#lua_upstream_skip_openssl_default_verify) * [lua_check_client_abort](https://github.com/openresty/lua-nginx-module#lua_check_client_abort) * [lua_max_pending_timers](https://github.com/openresty/lua-nginx-module#lua_max_pending_timers) * [lua_max_running_timers](https://github.com/openresty/lua-nginx-module#lua_max_running_timers) diff --git a/config b/config index e1470b7a..a6214034 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_verifyby.c \ $ngx_addon_dir/src/ngx_stream_lua_log_ringbuf.c \ $ngx_addon_dir/src/ngx_stream_lua_input_filters.c \ " @@ -322,6 +323,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_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 8406a653..8951d01e 100644 --- a/src/ngx_stream_lua_common.h +++ b/src/ngx_stream_lua_common.h @@ -135,6 +135,7 @@ #define NGX_STREAM_LUA_CONTEXT_PREREAD 0x0020 #define NGX_STREAM_LUA_CONTEXT_SSL_CERT 0x0040 #define NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO 0x0080 +#define NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY 0x0100 #define NGX_STREAM_LUA_FFI_NO_REQ_CTX -100 @@ -270,6 +271,14 @@ struct ngx_stream_lua_srv_conf_s { ngx_str_t ssl_client_hello_src; u_char *ssl_client_hello_src_key; } srv; + + struct { + 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; + + ngx_flag_t upstream_skip_openssl_default_verify; + } ups; #endif ngx_flag_t enable_code_cache; /* whether to enable diff --git a/src/ngx_stream_lua_control.c b/src/ngx_stream_lua_control.c index c3657007..245f2c59 100644 --- a/src/ngx_stream_lua_control.c +++ b/src/ngx_stream_lua_control.c @@ -116,14 +116,16 @@ ngx_stream_lua_ffi_exit(ngx_stream_lua_request_t *r, int status, u_char *err, | NGX_STREAM_LUA_CONTEXT_BALANCER | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_STREAM_LUA_CONTEXT_SSL_CERT - | NGX_STREAM_LUA_CONTEXT_PREREAD, + | NGX_STREAM_LUA_CONTEXT_PREREAD + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY, err, errlen) != NGX_OK) { return NGX_ERROR; } if (ctx->context & (NGX_STREAM_LUA_CONTEXT_SSL_CERT - | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO )) + | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY )) { #if (NGX_STREAM_SSL) diff --git a/src/ngx_stream_lua_coroutine.c b/src/ngx_stream_lua_coroutine.c index d4863dd0..4334fc45 100644 --- a/src/ngx_stream_lua_coroutine.c +++ b/src/ngx_stream_lua_coroutine.c @@ -205,6 +205,7 @@ ngx_stream_lua_coroutine_resume(lua_State *L) | NGX_STREAM_LUA_CONTEXT_TIMER | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_STREAM_LUA_CONTEXT_SSL_CERT + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY | NGX_STREAM_LUA_CONTEXT_PREREAD ); @@ -266,6 +267,7 @@ ngx_stream_lua_coroutine_yield(lua_State *L) | NGX_STREAM_LUA_CONTEXT_TIMER | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_STREAM_LUA_CONTEXT_SSL_CERT + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY | NGX_STREAM_LUA_CONTEXT_PREREAD ); @@ -426,6 +428,7 @@ ngx_stream_lua_coroutine_status(lua_State *L) | NGX_STREAM_LUA_CONTEXT_TIMER | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_STREAM_LUA_CONTEXT_SSL_CERT + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY | NGX_STREAM_LUA_CONTEXT_PREREAD ); diff --git a/src/ngx_stream_lua_ctx.c b/src/ngx_stream_lua_ctx.c index a8f9d5b9..fceca8af 100644 --- a/src/ngx_stream_lua_ctx.c +++ b/src/ngx_stream_lua_ctx.c @@ -97,7 +97,8 @@ ngx_stream_lua_ffi_get_ctx_ref(ngx_stream_lua_request_t *r, int *in_ssl_phase, } *in_ssl_phase = ctx->context & (NGX_STREAM_LUA_CONTEXT_SSL_CERT - | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO); + | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY); *ssl_ctx_ref = LUA_NOREF; #if (NGX_STREAM_SSL) @@ -131,7 +132,8 @@ ngx_stream_lua_ffi_set_ctx_ref(ngx_stream_lua_request_t *r, int ref) #if (NGX_STREAM_SSL) if (ctx->context & (NGX_STREAM_LUA_CONTEXT_SSL_CERT - | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO)) + | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY)) { ssl_ctx = ngx_stream_lua_ssl_get_ctx(r->connection->ssl->connection); if (ssl_ctx == NULL) { diff --git a/src/ngx_stream_lua_module.c b/src/ngx_stream_lua_module.c index 68a60ee0..709eb290 100644 --- a/src/ngx_stream_lua_module.c +++ b/src/ngx_stream_lua_module.c @@ -30,6 +30,7 @@ #include "ngx_stream_lua_semaphore.h" #include "ngx_stream_lua_ssl_client_helloby.h" #include "ngx_stream_lua_ssl_certby.h" +#include "ngx_stream_lua_proxy_ssl_verifyby.h" #include "ngx_stream_lua_prereadby.h" @@ -422,6 +423,28 @@ static ngx_command_t ngx_stream_lua_cmds[] = { 0, (void *) ngx_stream_lua_ssl_cert_handler_file }, + /* same context as proxy_pass directive */ + { 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, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + (void *) ngx_stream_lua_proxy_ssl_verify_handler_inline }, + + { ngx_string("proxy_ssl_verify_by_lua_file"), + NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_lua_proxy_ssl_verify_by_lua, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + (void *) ngx_stream_lua_proxy_ssl_verify_handler_file }, + + { ngx_string("lua_upstream_skip_openssl_default_verify"), + NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_lua_srv_conf_t, + ups.upstream_skip_openssl_default_verify), + NULL }, { ngx_string("lua_ssl_verify_depth"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, @@ -827,6 +850,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_verify_handler = NULL; + * lscf->ups.proxy_ssl_verify_src = { 0, NULL }; + * lscf->ups.proxy_ssl_verify_src_key = NULL; + * * lscf->srv.ssl_cert_handler = NULL; * lscf->srv.ssl_cert_src = { 0, NULL }; * lscf->srv.ssl_cert_src_key = NULL; @@ -868,6 +895,7 @@ ngx_stream_lua_create_srv_conf(ngx_conf_t *cf) conf->ssl_verify_depth = NGX_CONF_UNSET_UINT; conf->ssl_certificates = NGX_CONF_UNSET_PTR; conf->ssl_certificate_keys = NGX_CONF_UNSET_PTR; + conf->ups.upstream_skip_openssl_default_verify = NGX_CONF_UNSET; #endif return conf; @@ -1002,6 +1030,21 @@ ngx_stream_lua_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) NULL); #endif + 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; + conf->ups.proxy_ssl_verify_src_key = prev->ups.proxy_ssl_verify_src_key; + } + + if (conf->ups.proxy_ssl_verify_src.len) { + if (ngx_stream_lua_proxy_ssl_verify_set_callback(cf) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + ngx_conf_merge_value(conf->ups.upstream_skip_openssl_default_verify, + prev->ups.upstream_skip_openssl_default_verify, 0); + if (ngx_stream_lua_set_ssl(cf, conf) != NGX_OK) { return NGX_CONF_ERROR; } diff --git a/src/ngx_stream_lua_phase.c b/src/ngx_stream_lua_phase.c index 9ab0cfcf..6a299768 100644 --- a/src/ngx_stream_lua_phase.c +++ b/src/ngx_stream_lua_phase.c @@ -66,6 +66,10 @@ ngx_stream_lua_ngx_get_phase(lua_State *L) lua_pushliteral(L, "content"); break; + case NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY: + lua_pushliteral(L, "proxy_ssl_verify"); + break; + case NGX_STREAM_LUA_CONTEXT_LOG: lua_pushliteral(L, "log"); break; diff --git a/src/ngx_stream_lua_proxy_ssl_verifyby.c b/src/ngx_stream_lua_proxy_ssl_verifyby.c new file mode 100644 index 00000000..5d440203 --- /dev/null +++ b/src/ngx_stream_lua_proxy_ssl_verifyby.c @@ -0,0 +1,773 @@ +/* + * 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_proxy_ssl_verifyby.h" +#include "ngx_stream_lua_directive.h" +#include "ngx_stream_lua_ssl.h" + + +static void ngx_stream_lua_proxy_ssl_verify_done(void *data); +static void ngx_stream_lua_proxy_ssl_verify_aborted(void *data); +static u_char *ngx_stream_lua_log_proxy_ssl_verify_error(ngx_log_t *log, + u_char *buf, size_t len); +static ngx_int_t ngx_stream_lua_proxy_ssl_verify_by_chunk(lua_State *L, + ngx_stream_lua_request_t *r); + + +ngx_int_t +ngx_stream_lua_proxy_ssl_verify_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_verify_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_verify_by_lua* " + "should be used with proxy_ssl directive"); + + return NGX_ERROR; + } + +#if (!defined SSL_ERROR_WANT_RETRY_VERIFY \ + || OPENSSL_VERSION_NUMBER < 0x30000020L) + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "OpenSSL too old to support " + "proxy_ssl_verify_by_lua*"); + + return NGX_ERROR; + +#else + + SSL_CTX_set_cert_verify_callback(ssl->ctx, + ngx_stream_lua_proxy_ssl_verify_handler, + NULL); + return NGX_OK; + +#endif + +#endif +} + + +ngx_int_t +ngx_stream_lua_proxy_ssl_verify_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_verify_src.data, + lscf->ups.proxy_ssl_verify_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_verify_by_chunk(L, r); +} + + +ngx_int_t +ngx_stream_lua_proxy_ssl_verify_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_verify_src.data, + lscf->ups.proxy_ssl_verify_src.len, + lscf->ups.proxy_ssl_verify_src_key, + "=proxy_ssl_verify_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_verify_by_chunk(L, r); +} + + +char * +ngx_stream_lua_proxy_ssl_verify_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_verify_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_verify_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ +#if (!defined SSL_ERROR_WANT_RETRY_VERIFY \ + || OPENSSL_VERSION_NUMBER < 0x30000020L) + + /* SSL_set_retry_verify() was added in OpenSSL 3.0.2 */ + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "at least OpenSSL 3.0.2 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_verify_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_verify_handler = + (ngx_stream_lua_srv_conf_handler_pt) cmd->post; + + if (cmd->post == ngx_stream_lua_proxy_ssl_verify_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_verify_src.data = name; + lscf->ups.proxy_ssl_verify_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_verify_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_verify_src = value[1]; + + p = ngx_palloc(cf->pool, + sizeof("proxy_ssl_verify_by_lua") + + NGX_STREAM_LUA_INLINE_KEY_LEN); + if (p == NULL) { + return NGX_CONF_ERROR; + } + + lscf->ups.proxy_ssl_verify_src_key = p; + + p = ngx_copy(p, "proxy_ssl_verify_by_lua", + sizeof("proxy_ssl_verify_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 /* SSL_ERROR_WANT_RETRY_VERIFY */ +} + + +int +ngx_stream_lua_proxy_ssl_verify_handler(X509_STORE_CTX *x509_store, void *arg) +{ + lua_State *L; + ngx_int_t rc; + ngx_connection_t *c, *fc; + 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_core_srv_conf_t *cscf; + ngx_stream_session_t *s, *fs; + ngx_ssl_conn_t *ssl_conn; + + ssl_conn = X509_STORE_CTX_get_ex_data(x509_store, + SSL_get_ex_data_X509_STORE_CTX_idx()); + + c = ngx_ssl_get_connection(ssl_conn); /* connection to upstream */ + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "proxy ssl verify: connection reusable: %ud", c->reusable); + + cctx = ngx_stream_lua_ssl_get_ctx(c->ssl->connection); + + dd("proxy ssl verify handler, cert-verify-ctx=%p", cctx); + + if (cctx && cctx->entered_proxy_ssl_verify_handler) { + /* not the first time */ + + if (cctx->done) { + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "proxy_ssl_verify_by_lua: " + "cert verify callback exit code: %d", + cctx->exit_code); + + dd("lua proxy ssl verify done, finally"); + return cctx->exit_code; + } + + return SSL_set_retry_verify(ssl_conn); + } + + dd("first time"); + + ngx_reusable_connection(c, 0); + + s = c->data; + + fc = ngx_stream_lua_create_fake_connection(NULL); + if (fc == NULL) { + goto failed; + } + + fc->log->handler = ngx_stream_lua_log_proxy_ssl_verify_error; + fc->log->data = fc; + + fc->addr_text = c->addr_text; + fc->listening = c->listening; + + fs = ngx_stream_lua_create_fake_session(fc); + if (fs == NULL) { + goto failed; + } + + fs->main_conf = s->main_conf; + fs->srv_conf = s->srv_conf; + + r = ngx_stream_lua_create_fake_request(fs); + if (r == NULL) { + goto failed; + } + + fc->log->file = c->log->file; + fc->log->log_level = c->log->log_level; + fc->ssl = c->ssl; + + cscf = ngx_stream_get_module_srv_conf(fs, ngx_stream_core_module); + +#if defined(nginx_version) && nginx_version >= 1027001 + ngx_set_connection_log(fc, cscf->error_log); + +#else +#error "stream proxy_ssl_verify_by_lua only supports nginx >= 1.27.1" +#endif + + 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->exit_code = 1; /* successful by default */ + cctx->x509_store = x509_store; + cctx->connection = c; + cctx->request = r; + cctx->entered_proxy_ssl_verify_handler = 1; + cctx->done = 0; + + 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); + + if (lscf->ups.upstream_skip_openssl_default_verify == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "proxy_ssl_verify_by_lua: openssl default verify"); + + rc = X509_verify_cert(x509_store); + if (rc == 0) { + goto failed; + } + } + + /* TODO honor lua_code_cache off */ + L = ngx_stream_lua_get_lua_vm(r, NULL); + + c->log->action = "loading proxy ssl verify by lua"; + + if (lscf->ups.proxy_ssl_verify_handler == NULL) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "no proxy_ssl_verify_by_lua* defined in " + "server %s:%ui", &cscf->file_name, &cscf->line); + + goto failed; + } + + rc = lscf->ups.proxy_ssl_verify_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_verify_by_lua: handler return value: %i, " + "cert verify callback 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(fc->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->handler = ngx_stream_lua_proxy_ssl_verify_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_verify_aborted; + + return SSL_set_retry_verify(ssl_conn); + +#if 1 +failed: + + if (r && r->pool) { + ngx_stream_lua_free_fake_request(r); + } + + if (fc) { + ngx_stream_lua_close_fake_connection(fc); + } + + return 0; /* verify failure or error */ +#endif +} + + +static void +ngx_stream_lua_proxy_ssl_verify_done(void *data) +{ + ngx_connection_t *c; + ngx_stream_lua_ssl_ctx_t *cctx = data; + + dd("lua proxy ssl verify done"); + + if (cctx->aborted) { + return; + } + + ngx_stream_lua_assert(cctx->done == 0); + + cctx->done = 1; + + if (cctx->cleanup) { + *cctx->cleanup = NULL; + } + + c = cctx->connection; + + c->log->action = "proxy pass SSL handshaking"; + + ngx_post_event(c->write, &ngx_posted_events); +} + + +static void +ngx_stream_lua_proxy_ssl_verify_aborted(void *data) +{ + ngx_stream_lua_ssl_ctx_t *cctx = data; + + dd("lua proxy ssl verify aborted"); + + if (cctx->done) { + /* completed successfully already */ + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, cctx->connection->log, 0, + "proxy_ssl_verify_by_lua: cert verify callback aborted"); + + cctx->aborted = 1; + cctx->request->connection->ssl = NULL; + + ngx_stream_lua_finalize_fake_request(cctx->request, NGX_ERROR); +} + + +static u_char * +ngx_stream_lua_log_proxy_ssl_verify_error(ngx_log_t *log, u_char *buf, + size_t len) +{ + u_char *p; + ngx_connection_t *c; + + if (log->action) { + p = ngx_snprintf(buf, len, " while %s", log->action); + len -= p - buf; + buf = p; + } + + p = ngx_snprintf(buf, len, ", context: proxy_ssl_verify_by_lua*"); + len -= p - buf; + buf = p; + + c = log->data; + + if (c && c->addr_text.len) { + p = ngx_snprintf(buf, len, ", client: %V", &c->addr_text); + len -= p - buf; + buf = p; + } + + if (c && c->listening && c->listening->addr_text.len) { + p = ngx_snprintf(buf, len, ", server: %V", &c->listening->addr_text); + /* len -= p - buf; */ + buf = p; + } + + return buf; +} + + +static ngx_int_t +ngx_stream_lua_proxy_ssl_verify_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_stream_lua_cleanup_t *cln; + + 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) { + cln = ngx_stream_lua_cleanup_add(r, 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_VERIFY; + + 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; +} + + +/* + * openssl's doc of SSL_CTX_set_cert_verify_callback: + * In any case a viable verification result value must + * be reflected in the error member of x509_store_ctx, + * which can be done using X509_STORE_CTX_set_error. + */ +int +ngx_stream_lua_ffi_ssl_set_verify_result(ngx_stream_lua_request_t *r, + int verify_result, char **err) +{ +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + ngx_stream_lua_ssl_ctx_t *cctx; + X509_STORE_CTX *x509_store; + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + dd("get cctx session"); + + c = ngx_ssl_get_connection(ssl_conn); + + cctx = ngx_stream_lua_ssl_get_ctx(c->ssl->connection); + if (cctx == NULL) { + *err = "bad lua context"; + return NGX_ERROR; + } + + x509_store = cctx->x509_store; + + X509_STORE_CTX_set_error(x509_store, verify_result); + + return NGX_OK; +#else + *err = "OpenSSL too old to support this function"; + + return NGX_ERROR; +#endif +} + + +int +ngx_stream_lua_ffi_ssl_get_verify_result(ngx_stream_lua_request_t *r, + char **err) +{ +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + ngx_stream_lua_ssl_ctx_t *cctx; + X509_STORE_CTX *x509_store; + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + dd("get cctx session"); + + c = ngx_ssl_get_connection(ssl_conn); + + cctx = ngx_stream_lua_ssl_get_ctx(c->ssl->connection); + if (cctx == NULL) { + *err = "bad lua context"; + return NGX_ERROR; + } + + x509_store = cctx->x509_store; + + return X509_STORE_CTX_get_error(x509_store); +#else + *err = "OpenSSL too old to support this function"; + + return NGX_ERROR; +#endif +} + + +void +ngx_stream_lua_ffi_ssl_free_verify_cert(void *cdata) +{ + X509 *cert = cdata; + + X509_free(cert); +} + + +void * +ngx_stream_lua_ffi_ssl_get_verify_cert(ngx_stream_lua_request_t *r, char **err) +{ +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + ngx_stream_lua_ssl_ctx_t *cctx; + X509_STORE_CTX *x509_store; + X509 *x509; + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NULL; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NULL; + } + + dd("get cctx session"); + + c = ngx_ssl_get_connection(ssl_conn); + + cctx = ngx_stream_lua_ssl_get_ctx(c->ssl->connection); + if (cctx == NULL) { + *err = "bad lua context"; + return NULL; + } + + x509_store = cctx->x509_store; + + x509 = X509_STORE_CTX_get0_cert(x509_store); + + if (!X509_up_ref(x509)) { + *err = "get verify result failed"; + return NULL; + } + + return x509; +#else + *err = "OpenSSL too old to support this function"; + + return NULL; +#endif +} + +#endif /* NGX_STREAM_SSL */ diff --git a/src/ngx_stream_lua_proxy_ssl_verifyby.h b/src/ngx_stream_lua_proxy_ssl_verifyby.h new file mode 100644 index 00000000..4b6f516f --- /dev/null +++ b/src/ngx_stream_lua_proxy_ssl_verifyby.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + +#ifndef _NGX_STREAM_LUA_PROXY_SSL_VERIFYBY_H_INCLUDED_ +#define _NGX_STREAM_LUA_PROXY_SSL_VERIFYBY_H_INCLUDED_ + + +#include "ngx_stream_lua_common.h" + + +#if (NGX_STREAM_SSL) + +/* 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_verify_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_verify_handler_file( + ngx_stream_lua_request_t *r, ngx_stream_lua_srv_conf_t *lscf, lua_State *L); + +char *ngx_stream_lua_proxy_ssl_verify_by_lua_block(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); + +char *ngx_stream_lua_proxy_ssl_verify_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +int ngx_stream_lua_proxy_ssl_verify_handler(X509_STORE_CTX *x509_store, + void *arg); + +ngx_int_t ngx_stream_lua_proxy_ssl_verify_set_callback(ngx_conf_t *cf); + +#endif /* NGX_STREAM_SSL */ +#endif /* _NGX_STREAM_LUA_PROXY_SSL_VERIFYBY_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 a7aa40b8..f4101b64 100644 --- a/src/ngx_stream_lua_ssl.h +++ b/src/ngx_stream_lua_ssl.h @@ -32,9 +32,12 @@ typedef struct { ngx_str_t session_id; + X509_STORE_CTX *x509_store; + int exit_code; /* exit code for openssl's set_client_hello_cb or - set_cert_cb callback */ + set_cert_cb callback or + SSL_CTX_set_cert_verify_callback */ int ctx_ref; /* reference to anchor request ctx data in lua @@ -46,6 +49,7 @@ typedef struct { unsigned entered_client_hello_handler:1; unsigned entered_cert_handler:1; unsigned entered_sess_fetch_handler:1; + unsigned entered_proxy_ssl_verify_handler:1; } ngx_stream_lua_ssl_ctx_t; diff --git a/src/ngx_stream_lua_uthread.c b/src/ngx_stream_lua_uthread.c index 8d906de5..f66bb532 100644 --- a/src/ngx_stream_lua_uthread.c +++ b/src/ngx_stream_lua_uthread.c @@ -235,6 +235,7 @@ ngx_stream_lua_uthread_kill(lua_State *L) | NGX_STREAM_LUA_CONTEXT_PREREAD | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_STREAM_LUA_CONTEXT_SSL_CERT + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY | NGX_STREAM_LUA_CONTEXT_TIMER); coctx = ctx->cur_co_ctx; diff --git a/src/ngx_stream_lua_util.h b/src/ngx_stream_lua_util.h index 9c3a14eb..bde8716b 100644 --- a/src/ngx_stream_lua_util.h +++ b/src/ngx_stream_lua_util.h @@ -77,7 +77,8 @@ extern char ngx_stream_lua_headers_metatable_key; | NGX_STREAM_LUA_CONTEXT_CONTENT \ | NGX_STREAM_LUA_CONTEXT_TIMER \ | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO \ - | NGX_STREAM_LUA_CONTEXT_SSL_CERT) + | NGX_STREAM_LUA_CONTEXT_SSL_CERT \ + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY) #define ngx_stream_lua_context_name(c) \ @@ -90,6 +91,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_VERIFY ? \ + "proxy_ssl_verify_by_lua*" \ : "(unknown)") diff --git a/src/ngx_stream_lua_variable.c b/src/ngx_stream_lua_variable.c index 47eadc8d..d87ec018 100644 --- a/src/ngx_stream_lua_variable.c +++ b/src/ngx_stream_lua_variable.c @@ -49,7 +49,8 @@ ngx_stream_lua_ffi_var_get(ngx_stream_lua_request_t *r, u_char *name_data, if ((r)->connection->fd == (ngx_socket_t) -1) { ctx = ngx_stream_lua_get_module_ctx(r, ngx_stream_lua_module); if (ctx->context & (NGX_STREAM_LUA_CONTEXT_SSL_CERT - | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO)) + | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY)) { cctx = ngx_stream_lua_ssl_get_ctx(r->connection->ssl->connection); session = cctx->connection->data; diff --git a/t/164-proxy-ssl-verify-by.t b/t/164-proxy-ssl-verify-by.t new file mode 100644 index 00000000..243800c3 --- /dev/null +++ b/t/164-proxy-ssl-verify-by.t @@ -0,0 +1,889 @@ +# 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_verify_by_lua_block { + ngx.log(ngx.INFO, "hello world") + } +--- error_log +proxy_ssl_verify_by_lua* should be used with proxy_ssl directive +--- no_error_log +[error] +[alert] +--- must_die + + + +=== TEST 2: proxy_ssl_verify_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_verify_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_verify_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_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_by_lua_block { + ngx.log(ngx.INFO, "proxy_ssl_verify_by_lua is running!") + } +--- stream_response +it works! +--- error_log +proxy_ssl_verify_by_lua is running! +proxy_ssl_verify_by_lua: handler return value: 0, cert verify callback exit code: 1 +--- no_error_log +[error] +[alert] + + + +=== TEST 4: sleep +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_by_lua_block { + local begin = ngx.now() + ngx.sleep(0.1) + print("elapsed in proxy ssl verify by lua: ", ngx.now() - begin) + } +--- stream_response +it works! +--- error_log eval +qr/elapsed in proxy ssl verify by lua: 0.(?:09|1\d)\d+,/, +--- no_error_log +[error] +[alert] + + + +=== TEST 5: timer +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_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_verify_by_lua: handler return value: 0, cert verify callback exit code: 1 +--- no_error_log +[error] +[alert] + + + +=== TEST 6: cosocket +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_by_lua_block { + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if not ok then + ngx.log(ngx.ERR, "failed to connect to memc: ", err) + return + end + + local bytes, err = sock:send("flush_all\r\n") + if not bytes then + ngx.log(ngx.ERR, "failed to send flush_all command: ", err) + return + end + + local res, err = sock:receive() + if not res then + ngx.log(ngx.ERR, "failed to receive memc reply: ", err) + return + end + + print("received memc reply: ", res) + } +--- stream_response +it works! +--- error_log +received memc reply: OK +--- no_error_log +[error] +[alert] +--- SKIP + + + +=== TEST 7: ngx.exit(0) - no yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_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 8: ngx.exit(ngx.ERROR) - no yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_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_verify_by_lua: handler return value: -1, cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +should never reached here +[error] +[alert] +[emerg] + + + +=== TEST 9: ngx.exit(0) - yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_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 10: ngx.exit(ngx.ERROR) - yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_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_verify_by_lua: cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +should never reached here +[error] +[alert] +[emerg] + + + +=== TEST 11: lua exception - no yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_by_lua_block { + error("bad bad bad") + ngx.log(ngx.ERR, "should never reached here...") + } +--- error_log eval +[ +'runtime error: proxy_ssl_verify_by_lua:2: bad bad bad', +'proxy_ssl_verify_by_lua: handler return value: 500, cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 12: lua exception - yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_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_verify_by_lua:3: bad bad bad', +'proxy_ssl_verify_by_lua: cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 13: get phase +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_by_lua_block { + print("get_phase: ", ngx.get_phase()) + } +--- stream_response +it works! +--- error_log +get_phase: proxy_ssl_verify +--- no_error_log +[error] +[alert] + + + +=== TEST 14: simple logging (by_lua_file) +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_by_lua_file html/a.lua; +--- stream_response +it works! +--- user_files +>>> a.lua +print("proxy ssl verify by lua is running!") + +--- error_log +a.lua:1: proxy ssl verify by lua is running! +--- no_error_log +[error] +[alert] + + + +=== TEST 15: coroutine API +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_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 16: simple user thread wait with yielding +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_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 +uthread: hello in thread +uthread: done + + + +=== TEST 17: uthread (kill) +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_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() +uthread: killed +uthread: failed to kill: already waited or killed + + + +=== TEST 18: ngx.exit(ngx.OK) - no yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_by_lua_block { + ngx.exit(ngx.OK) + ngx.log(ngx.ERR, "should never reached here...") + } +--- stream_response +it works! +--- error_log eval +[ +'proxy_ssl_verify_by_lua: handler return value: 0, cert verify callback exit code: 1', +qr/\[debug\] .*? SSL_do_handshake: 1/, +'lua exit with code 0', +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 19: proxy_ssl_verify_by_lua* without yield API (simple logic) +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_by_lua_block { + print("proxy ssl verify: simple test start") + + -- Simple calculations without yield + local sum = 0 + for i = 1, 10 do + sum = sum + i + end + + print("proxy ssl verify: calculated sum: ", sum) + + -- String operations + local str = "hello" + str = str .. " world" + print("proxy ssl verify: 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 verify: table sum: ", count) + + print("proxy ssl verify: simple test done") + } +--- stream_response +it works! +--- grep_error_log eval: qr/(proxy ssl verify: simple test start|proxy ssl verify: calculated sum: 55|proxy ssl verify: concatenated string: hello world|proxy ssl verify: table sum: 6|proxy ssl verify: simple test done)/ +--- grep_error_log_out +proxy ssl verify: simple test start +proxy ssl verify: calculated sum: 55 +proxy ssl verify: concatenated string: hello world +proxy ssl verify: table sum: 6 +proxy ssl verify: simple test done + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 20: lua_upstream_skip_openssl_default_verify default off +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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_verify_by_lua_block { + ngx.log(ngx.INFO, "proxy ssl verify by lua is running!") + } +--- stream_response +it works! +--- error_log eval +[ +'proxy_ssl_verify_by_lua: openssl default verify', +'proxy_ssl_verify_by_lua: handler return value: 0, cert verify callback exit code: 1', +qr/\[debug\] .*? SSL_do_handshake: 1/, +] +--- no_error_log +[error] +[alert] + + + +=== TEST 21: lua_upstream_skip_openssl_default_verify on +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + 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; + + lua_upstream_skip_openssl_default_verify on; + + proxy_ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "proxy ssl verify by lua is running!") + } +--- stream_response +it works! +--- error_log eval +[ +'proxy ssl verify by lua is running!', +'proxy_ssl_verify_by_lua: handler return value: 0, cert verify callback exit code: 1', +qr/\[debug\] .*? SSL_do_handshake: 1/, +] +--- no_error_log +proxy_ssl_verify_by_lua: openssl default verify +[error] +[alert]