mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 04:10:24 +00:00
Provide better control over which HTTP protocols are enabled
Over cleartext TCP the `protocols` option lists the enabled protocols. The default is to allow both HTTP/1.1 and HTTP/2. Over TLS the default protocol to use when ALPN is not used can now be configured via the `alpn_default_protocol` option. Performing an HTTP/1.1 upgrade to HTTP/2 over TLS is now rejected with an error as connecting to HTTP/2 over TLS requires the use of ALPN (or that HTTP/2 be the default when connecting over TLS).
This commit is contained in:
parent
971684788d
commit
053e233c56
8 changed files with 160 additions and 28 deletions
|
@ -199,6 +199,73 @@ do_chunked_body(ChunkSize0, Data, Acc) ->
|
|||
do_chunked_body(ChunkSize, Rest,
|
||||
[iolist_to_binary(cow_http_te:chunk(Chunk))|Acc]).
|
||||
|
||||
disable_http1_tls(Config) ->
|
||||
doc("Ensure that we can disable HTTP/1.1 over TLS (force HTTP/2)."),
|
||||
TlsOpts = ct_helper:get_certs_from_ets(),
|
||||
{ok, _} = cowboy:start_tls(?FUNCTION_NAME, TlsOpts ++ [{port, 0}], #{
|
||||
env => #{dispatch => init_dispatch(Config)},
|
||||
alpn_default_protocol => http2
|
||||
}),
|
||||
Port = ranch:get_port(?FUNCTION_NAME),
|
||||
try
|
||||
{ok, Socket} = ssl:connect("localhost", Port,
|
||||
[binary, {active, false}|TlsOpts]),
|
||||
%% ALPN was not negotiated but we're still over HTTP/2.
|
||||
{error, protocol_not_negotiated} = ssl:negotiated_protocol(Socket),
|
||||
%% Send a valid preface.
|
||||
ok = ssl:send(Socket, [
|
||||
"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n",
|
||||
cow_http2:settings(#{})]),
|
||||
%% Receive the server preface.
|
||||
{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),
|
||||
{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),
|
||||
ok
|
||||
after
|
||||
cowboy:stop_listener(?FUNCTION_NAME)
|
||||
end.
|
||||
|
||||
disable_http2_prior_knowledge(Config) ->
|
||||
doc("Ensure that we can disable prior knowledge HTTP/2 upgrade."),
|
||||
{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{
|
||||
env => #{dispatch => init_dispatch(Config)},
|
||||
protocols => [http]
|
||||
}),
|
||||
Port = ranch:get_port(?FUNCTION_NAME),
|
||||
try
|
||||
{ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}]),
|
||||
%% Send a valid preface.
|
||||
ok = gen_tcp:send(Socket, [
|
||||
"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n",
|
||||
cow_http2:settings(#{})]),
|
||||
{ok, <<"HTTP/1.1 501">>} = gen_tcp:recv(Socket, 12, 1000),
|
||||
ok
|
||||
after
|
||||
cowboy:stop_listener(?FUNCTION_NAME)
|
||||
end.
|
||||
|
||||
disable_http2_upgrade(Config) ->
|
||||
doc("Ensure that we can disable HTTP/1.1 upgrade to HTTP/2."),
|
||||
{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{
|
||||
env => #{dispatch => init_dispatch(Config)},
|
||||
protocols => [http]
|
||||
}),
|
||||
Port = ranch:get_port(?FUNCTION_NAME),
|
||||
try
|
||||
{ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}]),
|
||||
%% Send a valid preface.
|
||||
ok = gen_tcp:send(Socket, [
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Host: localhost\r\n"
|
||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||
"Upgrade: h2c\r\n"
|
||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||
"\r\n"]),
|
||||
{ok, <<"HTTP/1.1 200">>} = gen_tcp:recv(Socket, 12, 1000),
|
||||
ok
|
||||
after
|
||||
cowboy:stop_listener(?FUNCTION_NAME)
|
||||
end.
|
||||
|
||||
hibernate(Config) ->
|
||||
doc("Ensure that we can enable hibernation for HTTP/1.1 connections."),
|
||||
{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{
|
||||
|
|
|
@ -35,8 +35,9 @@ all() -> [{group, clear}, {group, tls}].
|
|||
|
||||
groups() ->
|
||||
Tests = ct_helper:all(?MODULE),
|
||||
Clear = [T || T <- Tests, lists:sublist(atom_to_list(T), 4) =/= "alpn"] -- [prior_knowledge_reject_tls],
|
||||
TLS = [T || T <- Tests, lists:sublist(atom_to_list(T), 4) =:= "alpn"] ++ [prior_knowledge_reject_tls],
|
||||
RejectTLS = [http_upgrade_reject_tls, prior_knowledge_reject_tls],
|
||||
Clear = [T || T <- Tests, lists:sublist(atom_to_list(T), 4) =/= "alpn"] -- RejectTLS,
|
||||
TLS = [T || T <- Tests, lists:sublist(atom_to_list(T), 4) =:= "alpn"] ++ RejectTLS,
|
||||
[{clear, [parallel], Clear}, {tls, [parallel], TLS}].
|
||||
|
||||
init_per_group(Name = clear, Config) ->
|
||||
|
@ -68,6 +69,24 @@ init_routes(_) -> [
|
|||
|
||||
%% Starting HTTP/2 for "http" URIs.
|
||||
|
||||
http_upgrade_reject_tls(Config) ->
|
||||
doc("Implementations that support HTTP/2 over TLS must use ALPN. (RFC7540 3.4)"),
|
||||
TlsOpts = ct_helper:get_certs_from_ets(),
|
||||
{ok, Socket} = ssl:connect("localhost", config(port, Config),
|
||||
[binary, {active, false}|TlsOpts]),
|
||||
%% Send a valid preface.
|
||||
ok = ssl:send(Socket, [
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Host: localhost\r\n"
|
||||
"Connection: Upgrade, HTTP2-Settings\r\n"
|
||||
"Upgrade: h2c\r\n"
|
||||
"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
|
||||
"\r\n"]),
|
||||
%% We expect the server to send an HTTP 400 error
|
||||
%% when trying to use HTTP/2 without going through ALPN negotiation.
|
||||
{ok, <<"HTTP/1.1 400">>} = ssl:recv(Socket, 12, 1000),
|
||||
ok.
|
||||
|
||||
http_upgrade_ignore_h2(Config) ->
|
||||
doc("An h2 token in an Upgrade field must be ignored. (RFC7540 3.2)"),
|
||||
{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue