Skip to content

Commit

Permalink
feat(tls): introduce tls.disable_http2_alpn() function (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
oowl authored Oct 21, 2024
1 parent 05e0ed2 commit 3eb8966
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 4 deletions.
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
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()
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,
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]

0 comments on commit 3eb8966

Please sign in to comment.