Fixup TLS client options after resolving (#24)

This commit is contained in:
Andrew Mayorov 2022-10-31 17:30:00 +03:00
parent 865599dd87
commit 7358ceac9c
No known key found for this signature in database
GPG Key ID: 2837C62ACFBFED5D
5 changed files with 72 additions and 21 deletions

View File

@ -124,7 +124,7 @@ send(Url, Body, Options, ResOpts, WoodyState) ->
% MSPF-416: We resolve url host to an ip here to prevent
% reusing keep-alive connections to dead hosts
case woody_resolver:resolve_url(Url, WoodyState, ResOpts) of
{ok, {OldUrl, NewUrl}} ->
{ok, {OldUrl, NewUrl}, _ConnectOpts} ->
Headers = add_host_header(OldUrl, make_woody_headers(Context)),
Options1 = set_defaults(Options),
Options2 = set_timeouts(Options1, Context),

View File

@ -154,11 +154,16 @@ send_call(Buffer, #{url := Url} = Opts, WoodyState) ->
% MSPF-416: We resolve url host to an ip here to prevent
% reusing keep-alive connections to dead hosts
case woody_resolver:resolve_url(Url, WoodyState, ResolverOpts) of
{ok, {OldUrl, NewUrl}} ->
{ok, {OldUrl, NewUrl}, ConnectOpts} ->
Headers = add_host_header(OldUrl, make_woody_headers(Context)),
TransportOpts1 = set_defaults(TransportOpts),
TransportOpts2 = set_timeouts(TransportOpts1, Context),
Result = hackney:request(post, NewUrl, Headers, Buffer, maps:to_list(TransportOpts2)),
% NOTE
% This is to ensure hackney won't try to resolve original hostname again in
% `set_tls_overrides/2`.
TransportOpts3 = append_connect_opts(TransportOpts2, ConnectOpts),
TransportOpts4 = set_tls_overrides(TransportOpts3, OldUrl),
Result = hackney:request(post, NewUrl, Headers, Buffer, maps:to_list(TransportOpts4)),
handle_response(Result, WoodyState);
{error, Reason} ->
Error = {error, {resolve_failed, Reason}},
@ -203,6 +208,19 @@ calc_timeouts(Timeout) ->
T
end.
append_connect_opts(Options, ConnectOpts) ->
Options#{connect_options => maps:get(connect_options, Options, []) ++ ConnectOpts}.
set_tls_overrides(Options = #{ssl_options := _}, _OrigUrl) ->
Options;
set_tls_overrides(Options, #hackney_url{scheme = https, host = OrigHost}) ->
% NOTE
% Beware, we're abusing implementation details here.
SslOpts = hackney_connection:connect_options(hackney_ssl, OrigHost, maps:to_list(Options)),
Options#{ssl_options => SslOpts};
set_tls_overrides(Options, #hackney_url{scheme = _}) ->
Options.
-spec make_woody_headers(woody_context:ctx()) -> http_headers().
make_woody_headers(Context) ->
add_optional_headers(Context, [

View File

@ -36,13 +36,13 @@
%%
-spec resolve_url(url(), woody_state:st()) ->
{ok, resolve_result()}
{ok, resolve_result(), [gen_tcp:connect_option()]}
| {error, resolve_error()}.
resolve_url(Url, WoodyState) ->
resolve_url(Url, WoodyState, #{}).
-spec resolve_url(url(), woody_state:st(), options()) ->
{ok, resolve_result()}
{ok, resolve_result(), [gen_tcp:connect_option()]}
| {error, resolve_error()}.
resolve_url(Url, WoodyState, Opts) when is_list(Url) ->
resolve_url(unicode:characters_to_binary(Url), WoodyState, Opts);
@ -61,21 +61,28 @@ parse_url(Url) ->
resolve_parsed_url(ParsedUrl = #hackney_url{}, WoodyState, Opts) ->
case inet:parse_address(ParsedUrl#hackney_url.host) of
% url host is already an ip, move on
{ok, _} -> {ok, {ParsedUrl, ParsedUrl}};
{error, _} -> do_resolve_url(ParsedUrl, WoodyState, Opts)
{ok, IpAddr} ->
IpFamily =
case tuple_size(IpAddr) of
4 -> inet;
8 -> inet6
end,
{ok, {ParsedUrl, ParsedUrl}, [IpFamily]};
{error, _} ->
do_resolve_url(ParsedUrl, WoodyState, Opts)
end.
do_resolve_url(ParsedUrl, WoodyState, Opts) ->
UnresolvedHost = ParsedUrl#hackney_url.host,
_ = log_event(?EV_CLIENT_RESOLVE_BEGIN, WoodyState, #{host => UnresolvedHost}),
case lookup_host(UnresolvedHost, Opts) of
{ok, {IpAddr, _} = AddrInfo} ->
{ok, {IpAddr, IpFamily} = AddrInfo} ->
_ = log_event(?EV_CLIENT_RESOLVE_RESULT, WoodyState, #{
status => ok,
host => UnresolvedHost,
address => inet:ntoa(IpAddr)
}),
{ok, {ParsedUrl, replace_host(ParsedUrl, AddrInfo)}};
{ok, {ParsedUrl, replace_host(ParsedUrl, AddrInfo)}, [IpFamily]};
{error, Reason} ->
_ = log_event(?EV_CLIENT_RESOLVE_RESULT, WoodyState, #{
status => error,

View File

@ -23,7 +23,8 @@
-export([
client_wo_cert_test/1,
valid_client_cert_test/1,
invalid_client_cert_test/1
invalid_client_cert_test/1,
valid_cert_external_server/1
]).
%% woody_server_thrift_handler callback
@ -58,7 +59,8 @@ all() ->
[
{group, 'tlsv1.3'},
{group, 'tlsv1.2'},
{group, 'tlsv1.1'}
{group, 'tlsv1.1'},
valid_cert_external_server
].
-spec groups() -> [{group_name(), list(), [case_name()]}].
@ -141,6 +143,30 @@ invalid_client_cert_test(C) ->
{match, _} = re:run(Reason, <<"^{tls_alert,[\"\{]unknown[ _]ca.*$">>, [])
end.
-spec valid_cert_external_server(config()) -> _.
valid_cert_external_server(_C) ->
% NOTE
% This testcase needs internet connectivity.
% This testcase relies on correct TLS setup and implementation on example.org servers.
Url = <<"https://example.org/just-testing-tls-nevermind">>,
Context = woody_context:new(to_binary(?FUNCTION_NAME)),
Service = {?THRIFT_DEFS, 'Weapons'},
Options = #{
url => Url,
event_handler => {woody_ct_event_h, {client, external}}
},
try
_ = woody_client:call({Service, get_weapon, {<<"Example">>, <<>>}}, Options, Context),
error(unreachable)
catch
error:{woody_error, {
external,
result_unexpected,
<<"This server does not implement the woody protocol", _/binary>>
}} ->
ok
end.
-spec client_ssl_opts(atom()) -> [ssl:tls_client_option()].
client_ssl_opts('tlsv1.3') ->
% NOTE

View File

@ -945,30 +945,30 @@ calls_with_cache(_) ->
woody_resolver_inet(C) ->
WoodyState = woody_state:new(client, woody_context:new(), ?MODULE),
ok = inet_db:set_inet6(false),
{ok, ?RESPONSE(http, <<"127.0.0.1">>, <<"127.0.0.1">>, <<"/test">>)} =
{ok, ?RESPONSE(http, <<"127.0.0.1">>, <<"127.0.0.1">>, <<"/test">>), [inet]} =
woody_resolver:resolve_url(<<"http://127.0.0.1/test">>, WoodyState),
{ok, ?RESPONSE(http, <<"localhost">>, <<"127.0.0.1:80">>, <<"/test">>)} =
{ok, ?RESPONSE(http, <<"localhost">>, <<"127.0.0.1:80">>, <<"/test">>), [inet]} =
woody_resolver:resolve_url(<<"http://localhost/test">>, WoodyState),
{ok, ?RESPONSE(http, <<"localhost">>, <<"127.0.0.1:80">>, <<"/test?q=a">>)} =
{ok, ?RESPONSE(http, <<"localhost">>, <<"127.0.0.1:80">>, <<"/test?q=a">>), [inet]} =
woody_resolver:resolve_url("http://localhost/test?q=a", WoodyState),
{ok, ?RESPONSE(https, <<"localhost:8080">>, <<"127.0.0.1:8080">>, <<"/test">>)} =
{ok, ?RESPONSE(https, <<"localhost:8080">>, <<"127.0.0.1:8080">>, <<"/test">>), [inet]} =
woody_resolver:resolve_url(<<"https://localhost:8080/test">>, WoodyState),
{ok, ?RESPONSE(https, <<"localhost">>, <<"127.0.0.1:443">>, <<>>)} =
{ok, ?RESPONSE(https, <<"localhost">>, <<"127.0.0.1:443">>, <<>>), [inet]} =
woody_resolver:resolve_url(<<"https://localhost">>, WoodyState),
ok = inet_db:set_inet6(?config(env_inet6, C)).
woody_resolver_inet6(C) ->
WoodyState = woody_state:new(client, woody_context:new(), ?MODULE),
ok = inet_db:set_inet6(true),
{ok, ?RESPONSE(http, <<"[::1]">>, <<"[::1]">>, <<"/test">>)} =
{ok, ?RESPONSE(http, <<"[::1]">>, <<"[::1]">>, <<"/test">>), [inet6]} =
woody_resolver:resolve_url(<<"http://[::1]/test">>, WoodyState),
{ok, ?RESPONSE(http, <<"localhost">>, <<"[::1]:80">>, <<"/test">>)} =
{ok, ?RESPONSE(http, <<"localhost">>, <<"[::1]:80">>, <<"/test">>), [inet6]} =
woody_resolver:resolve_url(<<"http://localhost/test">>, WoodyState),
{ok, ?RESPONSE(http, <<"localhost">>, <<"[::1]:80">>, <<"/test?q=a">>)} =
{ok, ?RESPONSE(http, <<"localhost">>, <<"[::1]:80">>, <<"/test?q=a">>), [inet6]} =
woody_resolver:resolve_url("http://localhost/test?q=a", WoodyState),
{ok, ?RESPONSE(https, <<"localhost:8080">>, <<"[::1]:8080">>, <<"/test">>)} =
{ok, ?RESPONSE(https, <<"localhost:8080">>, <<"[::1]:8080">>, <<"/test">>), [inet6]} =
woody_resolver:resolve_url(<<"https://localhost:8080/test">>, WoodyState),
{ok, ?RESPONSE(https, <<"localhost">>, <<"[::1]:443">>, <<>>)} =
{ok, ?RESPONSE(https, <<"localhost">>, <<"[::1]:443">>, <<>>), [inet6]} =
woody_resolver:resolve_url(<<"https://localhost">>, WoodyState),
ok = inet_db:set_inet6(?config(env_inet6, C)).