Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce tls.disable_http2_alpn() function #93

Merged
merged 15 commits into from
Oct 21, 2024
3 changes: 1 addition & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
push:

env:
KONG_VERSION: master
KONG_VERSION: disable-h2-alpn-re
oowl marked this conversation as resolved.
Show resolved Hide resolved
BUILD_ROOT: ${{ github.workspace }}/kong/bazel-bin/build

concurrency:
Expand Down Expand Up @@ -135,7 +135,6 @@ jobs:
openssl version

prove -r t

- name: Run Test with Valgrind
run: |
source ${{ env.BUILD_ROOT }}/kong-dev-venv.sh
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
24 changes: 23 additions & 1 deletion lualib/resty/kong/tls.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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([[
Expand Down Expand Up @@ -333,6 +337,24 @@ do

error("unknown return code: " .. tostring(ret))
end

function _M.disable_http2_alpn()
oowl marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
3 changes: 3 additions & 0 deletions src/ngx_http_lua_kong_module.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
oowl marked this conversation as resolved.
Show resolved Hide resolved
ngx_flag_t enable_http2);

#endif /* _NGX_HTTP_LUA_KONG_MODULE_H_INCLUDED_ */
45 changes: 44 additions & 1 deletion src/ngx_http_lua_kong_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
167 changes: 167 additions & 0 deletions t/012-tls_disable_http2_alpn.t
Original file line number Diff line number Diff line change
@@ -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]
Loading