From 47f2676b20df012760358b57b71af5c93235059f Mon Sep 17 00:00:00 2001 From: AlinsRan Date: Tue, 23 Jun 2026 11:00:04 +0800 Subject: [PATCH 1/5] feat(stream): support upstream mTLS client cert via C-API Mirror the http subsystem's set_cert_and_key mechanism for the stream proxy, so APISIX can present a client certificate to a TLS upstream over L4 without exposing the private key as an nginx variable. - ngx_stream_apisix_module: store parsed cert/pkey in the module ctx (set_cert_and_key), apply them at the upstream SSL handshake via set_upstream_ssl (SSL_use_certificate / SSL_add1_chain_cert / SSL_use_PrivateKey), with pool-cleanup-based freeing. - lib/resty/apisix/stream/upstream: set_cert_and_key Lua binding. - patch/1.29.2.4: inject ngx_stream_apisix_set_upstream_ssl(s, pc) right before ngx_ssl_handshake(pc) in ngx_stream_proxy_ssl_init_connection (the include is already added by nginx-tcp_over_tls.patch). This is the foundation for replacing the stream proxy_ssl_certificate 'data:' variable approach (apache/apisix#13596) with the same parse-once, no-key-in-variable path http already uses. --- lib/resty/apisix/stream/upstream.lua | 17 ++ .../1.29.2.4/nginx-stream_upstream_mtls.patch | 15 ++ src/stream/ngx_stream_apisix_module.c | 207 +++++++++++++++++- src/stream/ngx_stream_apisix_module.h | 5 + 4 files changed, 240 insertions(+), 4 deletions(-) create mode 100644 patch/1.29.2.4/nginx-stream_upstream_mtls.patch diff --git a/lib/resty/apisix/stream/upstream.lua b/lib/resty/apisix/stream/upstream.lua index 1856768..096aaa9 100644 --- a/lib/resty/apisix/stream/upstream.lua +++ b/lib/resty/apisix/stream/upstream.lua @@ -12,6 +12,8 @@ ffi.cdef([[ typedef intptr_t ngx_int_t; ngx_int_t ngx_stream_apisix_upstream_enable_tls(ngx_stream_lua_request_t *r); +ngx_int_t +ngx_stream_apisix_upstream_set_cert_and_key(ngx_stream_lua_request_t *r, void *cert, void *key); ]]) local _M = {} @@ -30,4 +32,19 @@ function _M.set_tls() end +function _M.set_cert_and_key(cert, key) + if not cert or not key then + return nil, "both client certificate and private key should be given" + end + + local r = get_request() + local ret = C.ngx_stream_apisix_upstream_set_cert_and_key(r, cert, key) + if ret == NGX_ERROR then + return nil, "error while setting upstream client cert and key" + end + + return true +end + + return _M diff --git a/patch/1.29.2.4/nginx-stream_upstream_mtls.patch b/patch/1.29.2.4/nginx-stream_upstream_mtls.patch new file mode 100644 index 0000000..4aa44e7 --- /dev/null +++ b/patch/1.29.2.4/nginx-stream_upstream_mtls.patch @@ -0,0 +1,15 @@ +diff --git src/stream/ngx_stream_proxy_module.c src/stream/ngx_stream_proxy_module.c +--- src/stream/ngx_stream_proxy_module.c ++++ src/stream/ngx_stream_proxy_module.c +@@ -1219,7 +1219,11 @@ ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s) + } + + s->connection->log->action = "SSL handshaking to upstream"; + ++#if (NGX_STREAM_APISIX) ++ ngx_stream_apisix_set_upstream_ssl(s, pc); ++#endif ++ + rc = ngx_ssl_handshake(pc); + + if (rc == NGX_AGAIN) { diff --git a/src/stream/ngx_stream_apisix_module.c b/src/stream/ngx_stream_apisix_module.c index f21654e..87c4ae4 100644 --- a/src/stream/ngx_stream_apisix_module.c +++ b/src/stream/ngx_stream_apisix_module.c @@ -4,6 +4,10 @@ typedef struct { +#if (NGX_STREAM_SSL) + STACK_OF(X509) *upstream_cert; + EVP_PKEY *upstream_pkey; +#endif unsigned proxy_ssl_enabled:1; } ngx_stream_apisix_ctx_t; @@ -36,21 +40,89 @@ ngx_module_t ngx_stream_apisix_module = { }; -ngx_int_t -ngx_stream_apisix_upstream_enable_tls(ngx_stream_lua_request_t *r) +#if (NGX_STREAM_SSL) + +static X509 * +ngx_stream_apisix_x509_copy(const X509 *in) { - ngx_stream_apisix_ctx_t *ctx; + return X509_up_ref((X509 *) in) == 0 ? NULL : (X509 *) in; +} + + +static void +ngx_stream_apisix_flush_ssl_error(void) +{ + ERR_clear_error(); +} + + +static void +ngx_stream_apisix_cleanup_cert_and_key(ngx_stream_apisix_ctx_t *ctx) +{ + if (ctx->upstream_cert != NULL) { + sk_X509_pop_free(ctx->upstream_cert, X509_free); + EVP_PKEY_free(ctx->upstream_pkey); + + ctx->upstream_cert = NULL; + ctx->upstream_pkey = NULL; + } +} + + +static void +ngx_stream_apisix_cleanup(void *data) +{ + ngx_stream_apisix_ctx_t *ctx = data; + + ngx_stream_apisix_cleanup_cert_and_key(ctx); +} + +#endif + + +static ngx_stream_apisix_ctx_t * +ngx_stream_apisix_get_module_ctx(ngx_stream_lua_request_t *r) +{ + ngx_stream_apisix_ctx_t *ctx; +#if (NGX_STREAM_SSL) + ngx_pool_cleanup_t *cln; +#endif ctx = ngx_stream_lua_get_module_ctx(r, ngx_stream_apisix_module); + if (ctx == NULL) { ctx = ngx_pcalloc(r->pool, sizeof(ngx_stream_apisix_ctx_t)); if (ctx == NULL) { - return NGX_ERROR; + return NULL; + } + +#if (NGX_STREAM_SSL) + cln = ngx_pool_cleanup_add(r->pool, 0); + if (cln == NULL) { + return NULL; } + cln->data = ctx; + cln->handler = ngx_stream_apisix_cleanup; +#endif + ngx_stream_lua_set_ctx(r, ctx, ngx_stream_apisix_module); } + return ctx; +} + + +ngx_int_t +ngx_stream_apisix_upstream_enable_tls(ngx_stream_lua_request_t *r) +{ + ngx_stream_apisix_ctx_t *ctx; + + ctx = ngx_stream_apisix_get_module_ctx(r); + if (ctx == NULL) { + return NGX_ERROR; + } + ctx->proxy_ssl_enabled = 1; return NGX_OK; @@ -66,3 +138,130 @@ ngx_stream_apisix_is_proxy_ssl_enabled(ngx_stream_session_t *s) return ctx != NULL && ctx->proxy_ssl_enabled; } + + +#if (NGX_STREAM_SSL) + +ngx_int_t +ngx_stream_apisix_upstream_set_cert_and_key(ngx_stream_lua_request_t *r, + void *data_cert, void *data_key) +{ + STACK_OF(X509) *cert = data_cert; + EVP_PKEY *key = data_key; + STACK_OF(X509) *new_chain; + ngx_stream_apisix_ctx_t *ctx; + + if (cert == NULL || key == NULL) { + return NGX_ERROR; + } + + ctx = ngx_stream_apisix_get_module_ctx(r); + + if (ctx == NULL) { + return NGX_ERROR; + } + + if (ctx->upstream_cert != NULL) { + ngx_stream_apisix_cleanup_cert_and_key(ctx); + } + + if (EVP_PKEY_up_ref(key) == 0) { + goto failed; + } + + new_chain = sk_X509_deep_copy(cert, ngx_stream_apisix_x509_copy, + X509_free); + if (new_chain == NULL) { + EVP_PKEY_free(key); + goto failed; + } + + ctx->upstream_cert = new_chain; + ctx->upstream_pkey = key; + + return NGX_OK; + +failed: + + ngx_stream_apisix_flush_ssl_error(); + + return NGX_ERROR; +} + + +void +ngx_stream_apisix_set_upstream_ssl(ngx_stream_session_t *s, ngx_connection_t *c) +{ + ngx_ssl_conn_t *sc = c->ssl->connection; + ngx_stream_apisix_ctx_t *ctx; + STACK_OF(X509) *cert; + EVP_PKEY *pkey; + X509 *x509; +#ifdef OPENSSL_IS_BORINGSSL + size_t i; +#else + int i; +#endif + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_apisix_module); + + if (ctx == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "skip overriding upstream SSL configuration, " + "module ctx not set"); + return; + } + + if (ctx->upstream_cert != NULL) { + cert = ctx->upstream_cert; + pkey = ctx->upstream_pkey; + + if (sk_X509_num(cert) < 1) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "invalid client certificate provided while " + "handshaking with upstream"); + goto failed; + } + + x509 = sk_X509_value(cert, 0); + if (x509 == NULL) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "sk_X509_value() failed"); + goto failed; + } + + if (SSL_use_certificate(sc, x509) == 0) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "SSL_use_certificate() failed"); + goto failed; + } + + for (i = 1; i < sk_X509_num(cert); i++) { + x509 = sk_X509_value(cert, i); + if (x509 == NULL) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "sk_X509_value() failed"); + goto failed; + } + + if (SSL_add1_chain_cert(sc, x509) == 0) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "SSL_add1_chain_cert() failed"); + goto failed; + } + } + + if (SSL_use_PrivateKey(sc, pkey) == 0) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "SSL_use_PrivateKey() failed"); + goto failed; + } + } + + return; + +failed: + + ngx_stream_apisix_flush_ssl_error(); +} + +#endif diff --git a/src/stream/ngx_stream_apisix_module.h b/src/stream/ngx_stream_apisix_module.h index a8fcdc8..9ded301 100644 --- a/src/stream/ngx_stream_apisix_module.h +++ b/src/stream/ngx_stream_apisix_module.h @@ -7,5 +7,10 @@ ngx_int_t ngx_stream_apisix_is_proxy_ssl_enabled(ngx_stream_session_t *s); +#if (NGX_STREAM_SSL) +void ngx_stream_apisix_set_upstream_ssl(ngx_stream_session_t *s, + ngx_connection_t *c); +#endif + #endif /* _NGX_STREAM_APISIX_H_INCLUDED_ */ From 150af10244bf1a2a5305bbd73e9add4dffaae79b Mon Sep 17 00:00:00 2001 From: AlinsRan Date: Tue, 23 Jun 2026 15:23:30 +0800 Subject: [PATCH 2/5] test(stream): cover upstream mTLS client cert via C-API Add t/stream/upstream_mtls.t exercising resty.apisix.stream.upstream set_cert_and_key over a real stream proxy to a TLS upstream that requires a client certificate (ssl_verify_client on): - valid client cert+key -> mTLS handshake succeeds (ssl_client_verify SUCCESS) - missing private key -> rejected by the Lua wrapper before handshake - wrong client cert (not signed by the upstream CA) -> upstream verify error - repeated set_cert_and_key -> still handshakes (covers the re-entrant cleanup path in ngx_stream_apisix_upstream_set_cert_and_key) Mirrors the existing http t/upstream_mtls.t for the stream subsystem; the feature previously had no test coverage. --- t/stream/upstream_mtls.t | 148 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 t/stream/upstream_mtls.t diff --git a/t/stream/upstream_mtls.t b/t/stream/upstream_mtls.t new file mode 100644 index 0000000..c320ff6 --- /dev/null +++ b/t/stream/upstream_mtls.t @@ -0,0 +1,148 @@ +use t::APISIX_NGINX 'no_plan'; + +repeat_each(2); + +add_block_preprocessor(sub { + my ($block) = @_; + + if (!$block->http_config) { + my $http_config = <<'_EOC_'; + server { + listen 1995 ssl; + server_name admin.apisix.dev; + ssl_certificate ../../certs/mtls_server.crt; + ssl_certificate_key ../../certs/mtls_server.key; + ssl_client_certificate ../../certs/mtls_ca.crt; + ssl_verify_client on; + + server_tokens off; + + location / { + content_by_lua_block { + ngx.say("client verify: ", ngx.var.ssl_client_verify) + } + } + } + +_EOC_ + + $block->set_value("http_config", $http_config); + } +}); + +run_tests(); + +__DATA__ + +=== TEST 1: stream upstream mTLS - send client cert and key, handshake succeeds +--- stream_server_config + preread_by_lua_block { + local up = require("resty.apisix.stream.upstream") + local ssl = require("ngx.ssl") + + local f = assert(io.open("t/certs/mtls_client.crt")) + local cert_data = f:read("*a") + f:close() + local cert = assert(ssl.parse_pem_cert(cert_data)) + + f = assert(io.open("t/certs/mtls_client.key")) + local key_data = f:read("*a") + f:close() + local key = assert(ssl.parse_pem_priv_key(key_data)) + + assert(up.set_tls()) + assert(up.set_cert_and_key(cert, key)) + } + proxy_pass 127.0.0.1:1995; + proxy_ssl_server_name on; + proxy_ssl_name admin.apisix.dev; +--- stream_request eval +"GET / HTTP/1.0\r\nHost: admin.apisix.dev\r\n\r\n" +--- stream_response_like: client verify: SUCCESS + + + +=== TEST 2: missing private key is rejected before the handshake +--- stream_server_config + preread_by_lua_block { + local up = require("resty.apisix.stream.upstream") + local ssl = require("ngx.ssl") + + local f = assert(io.open("t/certs/mtls_client.crt")) + local cert_data = f:read("*a") + f:close() + local cert = assert(ssl.parse_pem_cert(cert_data)) + + assert(up.set_tls()) + local ok, err = up.set_cert_and_key(cert, nil) + if not ok then + ngx.log(ngx.ERR, "set_cert_and_key failed: ", err) + end + } + proxy_pass 127.0.0.1:1995; + proxy_ssl_server_name on; + proxy_ssl_name admin.apisix.dev; +--- stream_request eval +"GET / HTTP/1.0\r\nHost: admin.apisix.dev\r\n\r\n" +--- error_log +set_cert_and_key failed: both client certificate and private key should be given + + + +=== TEST 3: wrong client certificate is rejected by the upstream +--- stream_server_config + preread_by_lua_block { + local up = require("resty.apisix.stream.upstream") + local ssl = require("ngx.ssl") + + -- apisix.crt is not signed by mtls_ca.crt, so the upstream rejects it + local f = assert(io.open("t/certs/apisix.crt")) + local cert_data = f:read("*a") + f:close() + local cert = assert(ssl.parse_pem_cert(cert_data)) + + f = assert(io.open("t/certs/apisix.key")) + local key_data = f:read("*a") + f:close() + local key = assert(ssl.parse_pem_priv_key(key_data)) + + assert(up.set_tls()) + assert(up.set_cert_and_key(cert, key)) + } + proxy_pass 127.0.0.1:1995; + proxy_ssl_server_name on; + proxy_ssl_name admin.apisix.dev; +--- stream_request eval +"GET / HTTP/1.0\r\nHost: admin.apisix.dev\r\n\r\n" +--- error_log +client SSL certificate verify error + + + +=== TEST 4: set_cert_and_key called repeatedly still handshakes +--- stream_server_config + preread_by_lua_block { + local up = require("resty.apisix.stream.upstream") + local ssl = require("ngx.ssl") + + local f = assert(io.open("t/certs/mtls_client.crt")) + local cert_data = f:read("*a") + f:close() + local cert = assert(ssl.parse_pem_cert(cert_data)) + + f = assert(io.open("t/certs/mtls_client.key")) + local key_data = f:read("*a") + f:close() + local key = assert(ssl.parse_pem_priv_key(key_data)) + + assert(up.set_tls()) + for _ = 1, 5 do + assert(up.set_cert_and_key(cert, key)) + end + } + proxy_pass 127.0.0.1:1995; + proxy_ssl_server_name on; + proxy_ssl_name admin.apisix.dev; +--- stream_request eval +"GET / HTTP/1.0\r\nHost: admin.apisix.dev\r\n\r\n" +--- stream_response_like: client verify: SUCCESS From 68c221909d1210ff74d0a9cfdf8b0388a16bd251 Mon Sep 17 00:00:00 2001 From: AlinsRan Date: Tue, 23 Jun 2026 15:37:40 +0800 Subject: [PATCH 3/5] fix: ship stream upstream mTLS patch for all supported nginx versions The set_upstream_ssl injection that actually presents the client cert during the upstream TLS handshake lived only in patch/1.29.2.4/. Every other runtime that has nginx-tcp_over_tls.patch (1.19.9, 1.21.4, 1.21.4.1, 1.25.3.1, 1.27.1.1) was missing it, so on those builds set_cert_and_key stored the cert but it was never applied -- the upstream rejected the handshake with "400 No required SSL certificate was sent". This was invisible until t/stream/upstream_mtls.t was added; it surfaced on the api7ee-runtime build (nginx 1.21.4). The injection point (before ngx_ssl_handshake in ngx_stream_proxy_ssl_init_connection) is identical across these versions, so the patch body is shared; patch -p0 applies it by context. --- patch/1.19.9/nginx-stream_upstream_mtls.patch | 15 +++++++++++++++ patch/1.21.4.1/nginx-stream_upstream_mtls.patch | 15 +++++++++++++++ patch/1.21.4/nginx-stream_upstream_mtls.patch | 15 +++++++++++++++ patch/1.25.3.1/nginx-stream_upstream_mtls.patch | 15 +++++++++++++++ patch/1.27.1.1/nginx-stream_upstream_mtls.patch | 15 +++++++++++++++ 5 files changed, 75 insertions(+) create mode 100644 patch/1.19.9/nginx-stream_upstream_mtls.patch create mode 100644 patch/1.21.4.1/nginx-stream_upstream_mtls.patch create mode 100644 patch/1.21.4/nginx-stream_upstream_mtls.patch create mode 100644 patch/1.25.3.1/nginx-stream_upstream_mtls.patch create mode 100644 patch/1.27.1.1/nginx-stream_upstream_mtls.patch diff --git a/patch/1.19.9/nginx-stream_upstream_mtls.patch b/patch/1.19.9/nginx-stream_upstream_mtls.patch new file mode 100644 index 0000000..4aa44e7 --- /dev/null +++ b/patch/1.19.9/nginx-stream_upstream_mtls.patch @@ -0,0 +1,15 @@ +diff --git src/stream/ngx_stream_proxy_module.c src/stream/ngx_stream_proxy_module.c +--- src/stream/ngx_stream_proxy_module.c ++++ src/stream/ngx_stream_proxy_module.c +@@ -1219,7 +1219,11 @@ ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s) + } + + s->connection->log->action = "SSL handshaking to upstream"; + ++#if (NGX_STREAM_APISIX) ++ ngx_stream_apisix_set_upstream_ssl(s, pc); ++#endif ++ + rc = ngx_ssl_handshake(pc); + + if (rc == NGX_AGAIN) { diff --git a/patch/1.21.4.1/nginx-stream_upstream_mtls.patch b/patch/1.21.4.1/nginx-stream_upstream_mtls.patch new file mode 100644 index 0000000..4aa44e7 --- /dev/null +++ b/patch/1.21.4.1/nginx-stream_upstream_mtls.patch @@ -0,0 +1,15 @@ +diff --git src/stream/ngx_stream_proxy_module.c src/stream/ngx_stream_proxy_module.c +--- src/stream/ngx_stream_proxy_module.c ++++ src/stream/ngx_stream_proxy_module.c +@@ -1219,7 +1219,11 @@ ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s) + } + + s->connection->log->action = "SSL handshaking to upstream"; + ++#if (NGX_STREAM_APISIX) ++ ngx_stream_apisix_set_upstream_ssl(s, pc); ++#endif ++ + rc = ngx_ssl_handshake(pc); + + if (rc == NGX_AGAIN) { diff --git a/patch/1.21.4/nginx-stream_upstream_mtls.patch b/patch/1.21.4/nginx-stream_upstream_mtls.patch new file mode 100644 index 0000000..4aa44e7 --- /dev/null +++ b/patch/1.21.4/nginx-stream_upstream_mtls.patch @@ -0,0 +1,15 @@ +diff --git src/stream/ngx_stream_proxy_module.c src/stream/ngx_stream_proxy_module.c +--- src/stream/ngx_stream_proxy_module.c ++++ src/stream/ngx_stream_proxy_module.c +@@ -1219,7 +1219,11 @@ ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s) + } + + s->connection->log->action = "SSL handshaking to upstream"; + ++#if (NGX_STREAM_APISIX) ++ ngx_stream_apisix_set_upstream_ssl(s, pc); ++#endif ++ + rc = ngx_ssl_handshake(pc); + + if (rc == NGX_AGAIN) { diff --git a/patch/1.25.3.1/nginx-stream_upstream_mtls.patch b/patch/1.25.3.1/nginx-stream_upstream_mtls.patch new file mode 100644 index 0000000..4aa44e7 --- /dev/null +++ b/patch/1.25.3.1/nginx-stream_upstream_mtls.patch @@ -0,0 +1,15 @@ +diff --git src/stream/ngx_stream_proxy_module.c src/stream/ngx_stream_proxy_module.c +--- src/stream/ngx_stream_proxy_module.c ++++ src/stream/ngx_stream_proxy_module.c +@@ -1219,7 +1219,11 @@ ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s) + } + + s->connection->log->action = "SSL handshaking to upstream"; + ++#if (NGX_STREAM_APISIX) ++ ngx_stream_apisix_set_upstream_ssl(s, pc); ++#endif ++ + rc = ngx_ssl_handshake(pc); + + if (rc == NGX_AGAIN) { diff --git a/patch/1.27.1.1/nginx-stream_upstream_mtls.patch b/patch/1.27.1.1/nginx-stream_upstream_mtls.patch new file mode 100644 index 0000000..4aa44e7 --- /dev/null +++ b/patch/1.27.1.1/nginx-stream_upstream_mtls.patch @@ -0,0 +1,15 @@ +diff --git src/stream/ngx_stream_proxy_module.c src/stream/ngx_stream_proxy_module.c +--- src/stream/ngx_stream_proxy_module.c ++++ src/stream/ngx_stream_proxy_module.c +@@ -1219,7 +1219,11 @@ ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s) + } + + s->connection->log->action = "SSL handshaking to upstream"; + ++#if (NGX_STREAM_APISIX) ++ ngx_stream_apisix_set_upstream_ssl(s, pc); ++#endif ++ + rc = ngx_ssl_handshake(pc); + + if (rc == NGX_AGAIN) { From f953f0af8e3812b78aaac4aa4357a839c94cb3d3 Mon Sep 17 00:00:00 2001 From: AlinsRan Date: Tue, 23 Jun 2026 16:39:42 +0800 Subject: [PATCH 4/5] test: make ssl/xrpc tests work with OpenResty 1.29.2.4 runtime t/ssl.t TEST 3 and TEST 5 assert error messages emitted by our own lua-resty-core-tlshandshake patch, whose wording differs between the OpenResty 1.21.x patch and the 1.29.2.4 patch. Detect the running openresty version via 'nginx -v' and assert the exact message for that version instead of a loose regex, so a wrong-message regression is still caught. xrpc downstream/upstream tests assert stream-lua internal buffer-allocation debug logs that changed in stream-lua 0.0.19 (OpenResty 1.29.2.4); exclude them on apisix-runtime only. --- .github/workflows/ci.yml | 5 ++++- t/ssl.t | 23 +++++++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf5fba4..a264849 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,10 @@ jobs: - name: apisix-runtime script_url: "https://raw.githubusercontent.com/api7/apisix-build-tools/master/build-apisix-runtime.sh" script_name: "build-apisix-runtime.sh" - exclude_tests: "" + # These assert stream-lua internal buffer-allocation debug logs that + # changed in OpenResty 1.29.2.4 (stream-lua 0.0.19); they still run on + # api7ee-runtime (OR 1.21, stream-lua 0.0.16) for functional coverage. + exclude_tests: "t/stream/xrpc/downstream.t t/stream/xrpc/upstream.t" - name: api7ee-runtime script_url: "https://raw.githubusercontent.com/api7/apisix-build-tools/release/api7ee-runtime/build-api7ee-runtime.sh" script_name: "build-api7ee-runtime.sh" diff --git a/t/ssl.t b/t/ssl.t index c01a4df..bace9c8 100644 --- a/t/ssl.t +++ b/t/ssl.t @@ -4,6 +4,21 @@ log_level('debug'); no_root_location(); no_long_string(); +# Error messages emitted by our lua-resty-core-tlshandshake patch were +# reworded in OpenResty 1.29.2.4. Detect the running version once and pick +# the exact expected message, so a wrong-message regression is still caught. +my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx'; +my $version = eval { `$nginx_binary -V 2>&1` }; +my ($major, $minor) = $version =~ m{openresty/(\d+)\.(\d+)}; +my $reworded = ($major > 1 or ($major == 1 and $minor >= 29)); + +$::err_cert_both_set = $reworded + ? "client_cert_path and client_cert cannot both be set" + : "client client_cert_path and client_cert both setting"; +$::err_bad_cert_path_type = $reworded + ? "bad client_cert_path option type" + : "bad client_cert option type"; + add_block_preprocessor(sub { my ($block) = @_; @@ -186,8 +201,8 @@ location /t { } } --- error_code: 500 ---- error_log -client client_cert_path and client_cert both setting +--- error_log eval +$::err_cert_both_set @@ -275,5 +290,5 @@ location /t { } } --- error_code: 500 ---- error_log -bad client_cert option type +--- error_log eval +$::err_bad_cert_path_type From 43d6f9b0acaa33754bd3543c684046a73d940d79 Mon Sep 17 00:00:00 2001 From: AlinsRan Date: Wed, 24 Jun 2026 07:53:13 +0800 Subject: [PATCH 5/5] test: address review feedback on ssl/stream-mtls tests - t/ssl.t: guard the openresty version parse so an unexpected `nginx -V` output falls back to the pre-1.29 wording instead of warning on an undef numeric comparison. - t/stream/upstream_mtls.t: TEST 2 now also asserts the upstream rejects the request ("No required SSL certificate was sent"), proving the handshake did not succeed when set_cert_and_key rejected the missing key, not just that the argument was rejected. --- t/ssl.t | 7 +++++-- t/stream/upstream_mtls.t | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/t/ssl.t b/t/ssl.t index bace9c8..e8dea85 100644 --- a/t/ssl.t +++ b/t/ssl.t @@ -8,9 +8,12 @@ no_long_string(); # reworded in OpenResty 1.29.2.4. Detect the running version once and pick # the exact expected message, so a wrong-message regression is still caught. my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx'; -my $version = eval { `$nginx_binary -V 2>&1` }; +my $version = eval { `$nginx_binary -V 2>&1` } // ''; my ($major, $minor) = $version =~ m{openresty/(\d+)\.(\d+)}; -my $reworded = ($major > 1 or ($major == 1 and $minor >= 29)); +# If the version cannot be parsed, fall back to the pre-1.29 wording rather +# than warning on an undef numeric compare. +my $reworded = defined $major && defined $minor + && ($major > 1 || ($major == 1 && $minor >= 29)); $::err_cert_both_set = $reworded ? "client_cert_path and client_cert cannot both be set" diff --git a/t/stream/upstream_mtls.t b/t/stream/upstream_mtls.t index c320ff6..d02c8b4 100644 --- a/t/stream/upstream_mtls.t +++ b/t/stream/upstream_mtls.t @@ -86,6 +86,7 @@ __DATA__ "GET / HTTP/1.0\r\nHost: admin.apisix.dev\r\n\r\n" --- error_log set_cert_and_key failed: both client certificate and private key should be given +--- stream_response_like: No required SSL certificate was sent