From 3eb89666f84348fa0599d4e0a29ccf89511e8b75 Mon Sep 17 00:00:00 2001 From: Jun Ouyang Date: Mon, 21 Oct 2024 16:33:11 +0900 Subject: [PATCH] feat(tls): introduce `tls.disable_http2_alpn()` function (#93) --- .github/workflows/tests.yml | 3 +- README.md | 12 +++ lualib/resty/kong/tls.lua | 24 ++++- src/ngx_http_lua_kong_module.h | 3 + src/ngx_http_lua_kong_ssl.c | 45 ++++++++- t/012-tls_disable_http2_alpn.t | 167 +++++++++++++++++++++++++++++++++ 6 files changed, 250 insertions(+), 4 deletions(-) create mode 100644 t/012-tls_disable_http2_alpn.t diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 10d24289..e758d258 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,7 +5,7 @@ on: push: env: - KONG_VERSION: master + KONG_VERSION: disable-h2-alpn-re BUILD_ROOT: ${{ github.workspace }}/kong/bazel-bin/build concurrency: @@ -135,7 +135,6 @@ jobs: openssl version prove -r t - - name: Run Test with Valgrind run: | source ${{ env.BUILD_ROOT }}/kong-dev-venv.sh diff --git a/README.md b/README.md index a576fdde..b8d84f8d 100644 --- a/README.md +++ b/README.md @@ -380,6 +380,18 @@ Retrieves the OpenSSL `SSL*` object for the current HTTP request. On success, this function returns the pointer of type `SSL`. Otherwise `nil` and a string describing the error will be returned. +resty.kong.tls.disable\_http2\_alpn +---------------------------------------------------- +**syntax:** *ok, err = resty.kong.tls.disable\_http2\_alpn()* + +**context:** *client_hello_by_lua* + +**subsystems:** *http* + +Disables HTTP/2 ALPN negotiation for the current TLS connection. When called, the +connection will not negotiate HTTP/2 using ALPN and will fallback to HTTP/1.1 even though [`http2`](https://nginx.org/en/docs/http/ngx_http_v2_module.html#http2) directive is enabled. + +This function returns `true` when the call is successful. Otherwise it returns `false` and a string describing the error. [Back to TOC](#table-of-contents) diff --git a/lualib/resty/kong/tls.lua b/lualib/resty/kong/tls.lua index 648d5342..1cb9edc2 100644 --- a/lualib/resty/kong/tls.lua +++ b/lualib/resty/kong/tls.lua @@ -31,6 +31,8 @@ local get_string_buf = base.get_string_buf local size_ptr = base.get_size_ptr() local orig_get_request = base.get_request local subsystem = ngx.config.subsystem +local errmsg = base.get_errmsg_ptr() +local FFI_OK = base.FFI_OK base.allows_subsystem('http', 'stream') local kong_lua_kong_ffi_get_full_client_certificate_chain @@ -41,6 +43,7 @@ local kong_lua_kong_ffi_set_upstream_ssl_verify local kong_lua_kong_ffi_set_upstream_ssl_verify_depth local kong_lua_kong_ffi_get_socket_ssl local kong_lua_kong_ffi_get_request_ssl +local kong_lua_kong_ffi_disable_http2_alpn if subsystem == "http" then ffi.cdef([[ typedef struct ssl_st SSL; @@ -61,6 +64,7 @@ if subsystem == "http" then void **ssl_conn); int ngx_http_lua_kong_ffi_get_request_ssl(ngx_http_request_t *r, void **ssl_conn); + int ngx_http_lua_ffi_disable_http2_alpn(ngx_http_request_t *r, char **err); ]]) kong_lua_kong_ffi_get_full_client_certificate_chain = C.ngx_http_lua_kong_ffi_get_full_client_certificate_chain @@ -71,7 +75,7 @@ if subsystem == "http" then kong_lua_kong_ffi_set_upstream_ssl_verify_depth = C.ngx_http_lua_kong_ffi_set_upstream_ssl_verify_depth kong_lua_kong_ffi_get_socket_ssl = C.ngx_http_lua_kong_ffi_get_socket_ssl kong_lua_kong_ffi_get_request_ssl = C.ngx_http_lua_kong_ffi_get_request_ssl - + kong_lua_kong_ffi_disable_http2_alpn = C.ngx_http_lua_ffi_disable_http2_alpn elseif subsystem == 'stream' then ffi.cdef([[ @@ -333,6 +337,24 @@ do error("unknown return code: " .. tostring(ret)) end + + function _M.disable_http2_alpn() + if get_phase() ~= "ssl_client_hello" then + error("API disabled in the current context") + end + + local r = get_request() + if not r then + error("no request found") + end + + local rc = kong_lua_kong_ffi_disable_http2_alpn(r, errmsg) + if rc == FFI_OK then + return true + end + + return false, ffi_string(errmsg[0]) + end end if ngx.config.subsystem == "stream" then diff --git a/src/ngx_http_lua_kong_module.h b/src/ngx_http_lua_kong_module.h index 0996262b..528b3318 100644 --- a/src/ngx_http_lua_kong_module.h +++ b/src/ngx_http_lua_kong_module.h @@ -36,5 +36,8 @@ ngx_flag_t ngx_http_lua_kong_get_upstream_ssl_verify(ngx_http_request_t *r, ngx_flag_t proxy_ssl_verify); +ngx_flag_t +ngx_http_lua_kong_ssl_get_http2_alpn_enabled(ngx_ssl_connection_t *ssl, + ngx_flag_t enable_http2); #endif /* _NGX_HTTP_LUA_KONG_MODULE_H_INCLUDED_ */ diff --git a/src/ngx_http_lua_kong_ssl.c b/src/ngx_http_lua_kong_ssl.c index 7e56501b..9f819363 100644 --- a/src/ngx_http_lua_kong_ssl.c +++ b/src/ngx_http_lua_kong_ssl.c @@ -17,7 +17,8 @@ #include "ngx_http_lua_kong_common.h" #include "ngx_http_lua_socket_tcp.h" - +#include "ngx_http_lua_ssl.h" +#include "ngx_http_lua_util.h" /* * disables session reuse for the current TLS connection, must be called @@ -198,6 +199,48 @@ ngx_http_lua_kong_get_upstream_ssl_verify(ngx_http_request_t *r, return ngx_lua_kong_ssl_get_upstream_ssl_verify(&ctx->ssl_ctx, proxy_ssl_verify); } +ngx_flag_t +ngx_http_lua_kong_ssl_get_http2_alpn_enabled(ngx_ssl_connection_t *ssl, + ngx_flag_t enable_http2) +{ + ngx_http_lua_ssl_ctx_t *cctx; + + cctx = ngx_http_lua_ssl_get_ctx(ssl->connection); + if (cctx && cctx->disable_http2_alpn) { + return 0; + } + + return enable_http2; +} + +int +ngx_http_lua_ffi_disable_http2_alpn(ngx_http_request_t *r, char **err) +{ + ngx_ssl_conn_t *ssl_conn; + ngx_http_lua_ssl_ctx_t *cctx; + + 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; + } + + cctx = ngx_http_lua_ssl_get_ctx(ssl_conn); + if (cctx == NULL) { + *err = "bad lua context"; + return NGX_ERROR; + } + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "lua ssl disable http2"); + cctx->disable_http2_alpn = 1; + + return NGX_OK; +} #endif diff --git a/t/012-tls_disable_http2_alpn.t b/t/012-tls_disable_http2_alpn.t new file mode 100644 index 00000000..89a9d95f --- /dev/null +++ b/t/012-tls_disable_http2_alpn.t @@ -0,0 +1,167 @@ +# vim:set ft= ts=4 sw=4 et: + +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 7 - 2); + +my $pwd = cwd(); + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +log_level('info'); +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ + +=== TEST 1: normal http2 alpn +--- http_config + lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + listen 60000 ssl; + server_name example.com; + ssl_certificate ../../cert/example.com.crt; + ssl_certificate_key ../../cert/example.com.key; + ssl_session_cache off; + ssl_session_tickets on; + server_tokens off; + http2 on; + ssl_client_hello_by_lua_block { + local tls = require("resty.kong.tls") + } + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.exit(200)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + location /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({'curl', '-vk', '--resolve', 'example.com:60000:127.0.0.1', 'https://example.com:60000'}) + local stdout_data, err = proc:stdout_read_all() + if not stdout_data then + ngx.say(err) + return + end + + local stderr_data, err = proc:stderr_read_all() + if not stderr_data then + ngx.say(err) + return + end + + if string.find(stderr_data, "ALPN: server accepted h2") ~= nil then + ngx.say("alpn server accepted h2") + return + end + + if string.find(stderr_data, "ALPN: server accepted http/1.1") ~= nil then + ngx.say("alpn server accepted http/1.1") + return + end + if string.find(stderr_data, "ALPN, server accepted to use h2") ~= nil then + ngx.say("alpn server accepted h2") + return + end + + if string.find(stderr_data, " ALPN, server accepted to use http/1.1") ~= nil then + ngx.say("alpn server accepted http/1.1") + return + end + } + } +--- request +GET /t +--- response_body +alpn server accepted h2 +--- no_error_log +[error] +[alert] +[warn] +[crit] + +=== TEST 2: disable http2 alpn +--- http_config + lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + listen 60000 ssl; + server_name example.com; + ssl_certificate ../../cert/example.com.crt; + ssl_certificate_key ../../cert/example.com.key; + ssl_session_cache off; + ssl_session_tickets on; + server_tokens off; + http2 on; + ssl_client_hello_by_lua_block { + local tls = require("resty.kong.tls") + local ok, err = tls.disable_http2_alpn() + if not ok then + ngx.log(ngx.ERR, "failed to disable http2") + end + } + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.exit(200)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + location /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({'curl', '-vk', '--resolve', 'example.com:60000:127.0.0.1', 'https://example.com:60000'}) + local stdout_data, err = proc:stdout_read_all() + if not stdout_data then + ngx.say(err) + return + end + + local stderr_data, err = proc:stderr_read_all() + if not stderr_data then + ngx.say(err) + return + end + + if string.find(stderr_data, "ALPN: server accepted h2") ~= nil then + ngx.say("alpn server accepted h2") + return + end + + if string.find(stderr_data, "ALPN: server accepted http/1.1") ~= nil then + ngx.say("alpn server accepted http/1.1") + return + end + + if string.find(stderr_data, "ALPN, server accepted to use h2") ~= nil then + ngx.say("alpn server accepted h2") + return + end + + if string.find(stderr_data, " ALPN, server accepted to use http/1.1") ~= nil then + ngx.say("alpn server accepted http/1.1") + return + end + } + } +--- request +GET /t +--- response_body +alpn server accepted http/1.1 +--- no_error_log +[error] +[alert] +[warn] +[crit] \ No newline at end of file