2019-06-06 10:11:48 +00:00
|
|
|
-module(woody_ssl_SUITE).
|
|
|
|
|
|
|
|
-include_lib("public_key/include/public_key.hrl").
|
|
|
|
-include_lib("common_test/include/ct.hrl").
|
|
|
|
-include_lib("stdlib/include/assert.hrl").
|
2021-01-15 12:35:41 +00:00
|
|
|
|
2019-06-06 10:11:48 +00:00
|
|
|
-include("woody_test_thrift.hrl").
|
|
|
|
|
|
|
|
-behaviour(supervisor).
|
|
|
|
-behaviour(woody_server_thrift_handler).
|
|
|
|
|
|
|
|
%% common test callbacks
|
|
|
|
-export([
|
|
|
|
all/0,
|
2020-06-30 12:19:53 +00:00
|
|
|
groups/0,
|
2019-06-06 10:11:48 +00:00
|
|
|
init_per_suite/1,
|
2020-06-30 12:19:53 +00:00
|
|
|
end_per_suite/1,
|
|
|
|
init_per_group/2,
|
|
|
|
end_per_group/2
|
2019-06-06 10:11:48 +00:00
|
|
|
]).
|
|
|
|
|
|
|
|
%% tests
|
|
|
|
-export([
|
|
|
|
client_wo_cert_test/1,
|
|
|
|
valid_client_cert_test/1,
|
2022-10-31 14:30:00 +00:00
|
|
|
invalid_client_cert_test/1,
|
|
|
|
valid_cert_external_server/1
|
2019-06-06 10:11:48 +00:00
|
|
|
]).
|
|
|
|
|
|
|
|
%% woody_server_thrift_handler callback
|
|
|
|
-export([handle_function/4]).
|
|
|
|
|
|
|
|
%% supervisor callback
|
|
|
|
-export([init/1]).
|
|
|
|
|
2021-01-15 12:35:41 +00:00
|
|
|
-type config() :: [{atom(), any()}].
|
2020-06-30 12:19:53 +00:00
|
|
|
-type group_name() :: atom().
|
2019-06-06 10:11:48 +00:00
|
|
|
-type case_name() :: atom().
|
|
|
|
|
|
|
|
-define(PATH, "/v1/test/weapons").
|
|
|
|
-define(THRIFT_DEFS, woody_test_thrift).
|
|
|
|
|
|
|
|
-define(data_dir(C), ?config(data_dir, C)).
|
|
|
|
-define(valid_subdir(C), filename:join(?data_dir(C), "valid")).
|
|
|
|
-define(invalid_subdir(C), filename:join(?data_dir(C), "invalid")).
|
|
|
|
|
|
|
|
-define(ca_cert(C), filename:join(?valid_subdir(C), "ca.crt")).
|
|
|
|
-define(server_cert(C), filename:join(?valid_subdir(C), "server.pem")).
|
|
|
|
-define(client_cert(C), filename:join(?valid_subdir(C), "client.pem")).
|
|
|
|
|
|
|
|
-define(invalid_client_cert(C), filename:join(?invalid_subdir(C), "client.pem")).
|
|
|
|
|
|
|
|
%%%
|
|
|
|
%%% CT callbacks
|
|
|
|
%%%
|
|
|
|
|
2021-01-15 12:35:41 +00:00
|
|
|
-spec all() -> [{group, group_name()}].
|
2019-06-06 10:11:48 +00:00
|
|
|
all() ->
|
|
|
|
[
|
2020-06-30 12:19:53 +00:00
|
|
|
{group, 'tlsv1.3'},
|
|
|
|
{group, 'tlsv1.2'},
|
2022-10-31 14:30:00 +00:00
|
|
|
{group, 'tlsv1.1'},
|
|
|
|
valid_cert_external_server
|
2020-06-30 12:19:53 +00:00
|
|
|
].
|
|
|
|
|
2021-01-15 12:35:41 +00:00
|
|
|
-spec groups() -> [{group_name(), list(), [case_name()]}].
|
2020-06-30 12:19:53 +00:00
|
|
|
groups() ->
|
|
|
|
TestGroup = [
|
2019-06-06 10:11:48 +00:00
|
|
|
client_wo_cert_test,
|
|
|
|
invalid_client_cert_test,
|
|
|
|
valid_client_cert_test
|
2020-06-30 12:19:53 +00:00
|
|
|
],
|
|
|
|
[
|
|
|
|
{'tlsv1.1', [parallel], TestGroup},
|
|
|
|
{'tlsv1.2', [parallel], TestGroup},
|
|
|
|
{'tlsv1.3', [parallel], TestGroup}
|
2019-06-06 10:11:48 +00:00
|
|
|
].
|
|
|
|
|
2021-01-15 12:35:41 +00:00
|
|
|
-spec init_per_suite(config()) -> config().
|
2019-06-06 10:11:48 +00:00
|
|
|
init_per_suite(C) ->
|
2021-01-15 12:35:41 +00:00
|
|
|
{ok, Sup} = supervisor:start_link({local, ?MODULE}, ?MODULE, []),
|
|
|
|
true = erlang:unlink(Sup),
|
|
|
|
{ok, Apps} = application:ensure_all_started(woody),
|
2020-06-30 12:19:53 +00:00
|
|
|
[{sup, Sup}, {apps, Apps} | C].
|
2019-06-06 10:11:48 +00:00
|
|
|
|
2021-01-15 12:35:41 +00:00
|
|
|
-spec end_per_suite(config()) -> ok.
|
2019-06-06 10:11:48 +00:00
|
|
|
end_per_suite(C) ->
|
|
|
|
Sup = ?config(sup, C),
|
2021-01-15 12:35:41 +00:00
|
|
|
ok = proc_lib:stop(Sup),
|
|
|
|
_ = [application:stop(App) || App <- proplists:get_value(apps, C)],
|
2019-06-06 10:11:48 +00:00
|
|
|
ok.
|
|
|
|
|
2021-01-15 12:35:41 +00:00
|
|
|
-spec init_per_group(group_name(), config()) -> config().
|
2020-06-30 12:19:53 +00:00
|
|
|
init_per_group(Name, C) ->
|
|
|
|
{ok, WoodyServer} = start_woody_server(Name, C),
|
|
|
|
[{woody_server, WoodyServer}, {group_name, Name} | C].
|
|
|
|
|
2021-01-15 12:35:41 +00:00
|
|
|
-spec end_per_group(group_name(), config()) -> any().
|
2020-06-30 12:19:53 +00:00
|
|
|
end_per_group(_Name, C) ->
|
|
|
|
stop_woody_server(C).
|
|
|
|
|
2019-06-06 10:11:48 +00:00
|
|
|
%%%
|
|
|
|
%%% Tests
|
|
|
|
%%%
|
|
|
|
|
|
|
|
-spec client_wo_cert_test(config()) -> _.
|
|
|
|
client_wo_cert_test(C) ->
|
2020-06-30 12:19:53 +00:00
|
|
|
Vsn = ?config(group_name, C),
|
|
|
|
SSLOptions = [{cacertfile, ?ca_cert(C)} | client_ssl_opts(Vsn)],
|
2019-06-19 15:32:52 +00:00
|
|
|
try
|
2021-06-22 08:01:27 +00:00
|
|
|
_ = get_weapon(?FUNCTION_NAME, <<"BFG">>, Vsn, SSLOptions),
|
2019-06-19 15:32:52 +00:00
|
|
|
error(unreachable)
|
|
|
|
catch
|
2020-06-30 12:19:53 +00:00
|
|
|
% NOTE
|
|
|
|
% Seems that TLSv1.3 connection setup kinda racy.
|
|
|
|
error:{woody_error, {internal, result_unexpected, Reason}} when Vsn =:= 'tlsv1.3' ->
|
|
|
|
?assertMatch(<<"{tls_alert,{certificate_required", _/binary>>, Reason);
|
|
|
|
error:{woody_error, {external, result_unknown, <<"closed">>}} when Vsn =:= 'tlsv1.3' ->
|
|
|
|
ok;
|
2019-06-19 15:32:52 +00:00
|
|
|
error:{woody_error, {internal, result_unexpected, Reason}} ->
|
|
|
|
{match, _} = re:run(Reason, <<"^{tls_alert,[\"\{]handshake[ _]failure.*$">>, [])
|
|
|
|
end.
|
2019-06-06 10:11:48 +00:00
|
|
|
|
|
|
|
-spec valid_client_cert_test(config()) -> _.
|
|
|
|
valid_client_cert_test(C) ->
|
2020-06-30 12:19:53 +00:00
|
|
|
Vsn = ?config(group_name, C),
|
|
|
|
SSLOptions = [{cacertfile, ?ca_cert(C)}, {certfile, ?client_cert(C)} | client_ssl_opts(Vsn)],
|
2021-06-22 08:01:27 +00:00
|
|
|
{ok, #'Weapon'{}} = get_weapon(?FUNCTION_NAME, <<"BFG">>, Vsn, SSLOptions).
|
2019-06-06 10:11:48 +00:00
|
|
|
|
|
|
|
-spec invalid_client_cert_test(config()) -> _.
|
|
|
|
invalid_client_cert_test(C) ->
|
2020-06-30 12:19:53 +00:00
|
|
|
Vsn = ?config(group_name, C),
|
|
|
|
SSLOptions = [{cacertfile, ?ca_cert(C)}, {certfile, ?invalid_client_cert(C)} | client_ssl_opts(Vsn)],
|
2019-06-19 15:32:52 +00:00
|
|
|
try
|
2021-06-22 08:01:27 +00:00
|
|
|
_ = get_weapon(?FUNCTION_NAME, <<"BFG">>, Vsn, SSLOptions),
|
2019-06-19 15:32:52 +00:00
|
|
|
error(unreachable)
|
|
|
|
catch
|
2020-06-30 12:19:53 +00:00
|
|
|
% NOTE
|
|
|
|
% Seems that TLSv1.3 connection setup kinda racy.
|
|
|
|
error:{woody_error, {external, result_unknown, <<"closed">>}} when Vsn =:= 'tlsv1.3' ->
|
|
|
|
ok;
|
2019-06-19 15:32:52 +00:00
|
|
|
error:{woody_error, {internal, result_unexpected, Reason}} ->
|
|
|
|
{match, _} = re:run(Reason, <<"^{tls_alert,[\"\{]unknown[ _]ca.*$">>, [])
|
|
|
|
end.
|
2019-06-06 10:11:48 +00:00
|
|
|
|
2022-10-31 14:30:00 +00:00
|
|
|
-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.
|
|
|
|
|
2021-01-15 12:35:41 +00:00
|
|
|
-spec client_ssl_opts(atom()) -> [ssl:tls_client_option()].
|
2020-06-30 12:19:53 +00:00
|
|
|
client_ssl_opts('tlsv1.3') ->
|
|
|
|
% NOTE
|
2020-08-04 11:47:06 +00:00
|
|
|
% We need at least an extra TLSv1.2 here and default OTP cipher suites,
|
|
|
|
% otherwise hackney messes up client options.
|
|
|
|
[
|
|
|
|
{versions, ['tlsv1.3', 'tlsv1.2']},
|
|
|
|
{ciphers, ssl:cipher_suites(default, 'tlsv1.3')}
|
|
|
|
];
|
2020-06-30 12:19:53 +00:00
|
|
|
client_ssl_opts(Vsn) ->
|
|
|
|
[{versions, [Vsn]}].
|
|
|
|
|
2019-06-06 10:11:48 +00:00
|
|
|
%%%
|
|
|
|
%%% woody_server_thrift_handler callback
|
|
|
|
%%%
|
|
|
|
|
2021-01-15 12:35:41 +00:00
|
|
|
-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) -> {ok, woody:result()}.
|
2020-08-18 10:01:47 +00:00
|
|
|
handle_function(get_weapon, {Name, _Data}, Context, _Opts) ->
|
2019-06-24 16:44:45 +00:00
|
|
|
_ = assert_common_name([<<"Valid Test Client">>], Context),
|
2019-06-06 10:11:48 +00:00
|
|
|
{ok, #'Weapon'{name = Name, slot_pos = 0}}.
|
|
|
|
|
|
|
|
%%%
|
|
|
|
%%% Supervisor callback
|
|
|
|
%%%
|
|
|
|
|
2021-01-15 12:35:41 +00:00
|
|
|
-spec init(_) -> genlib_gen:supervisor_ret().
|
2019-06-06 10:11:48 +00:00
|
|
|
init(_) ->
|
2021-01-15 12:35:41 +00:00
|
|
|
{ok,
|
|
|
|
{
|
|
|
|
{one_for_one, 1, 1},
|
|
|
|
[]
|
|
|
|
}}.
|
2019-06-06 10:11:48 +00:00
|
|
|
|
|
|
|
%%%
|
|
|
|
%%% Internal functions
|
|
|
|
%%%
|
|
|
|
|
2020-06-30 12:19:53 +00:00
|
|
|
start_woody_server(Vsn, C) ->
|
2019-06-06 10:11:48 +00:00
|
|
|
Sup = ?config(sup, C),
|
|
|
|
Server = woody_server:child_spec(?MODULE, #{
|
2021-01-15 12:35:41 +00:00
|
|
|
handlers => [{?PATH, {{?THRIFT_DEFS, 'Weapons'}, ?MODULE}}],
|
2021-06-22 08:01:27 +00:00
|
|
|
event_handler => {woody_ct_event_h, {server, Vsn}},
|
2021-01-15 12:35:41 +00:00
|
|
|
ip => {0, 0, 0, 0},
|
|
|
|
port => 8043,
|
2019-06-06 10:11:48 +00:00
|
|
|
transport_opts => #{
|
2021-01-15 12:35:41 +00:00
|
|
|
transport => ranch_ssl,
|
2019-06-06 10:11:48 +00:00
|
|
|
socket_opts => [
|
2021-01-15 12:35:41 +00:00
|
|
|
{cacertfile, ?ca_cert(C)},
|
|
|
|
{certfile, ?server_cert(C)},
|
|
|
|
{verify, verify_peer},
|
2020-06-30 12:19:53 +00:00
|
|
|
{fail_if_no_peer_cert, true},
|
2021-01-15 12:35:41 +00:00
|
|
|
{versions, [Vsn]}
|
2019-06-06 10:11:48 +00:00
|
|
|
]
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
supervisor:start_child(Sup, Server).
|
|
|
|
|
2021-01-15 12:35:41 +00:00
|
|
|
-spec stop_woody_server(config()) -> ok.
|
2020-06-30 12:19:53 +00:00
|
|
|
stop_woody_server(C) ->
|
|
|
|
ok = supervisor:terminate_child(?config(sup, C), ?MODULE),
|
|
|
|
ok = supervisor:delete_child(?config(sup, C), ?MODULE).
|
|
|
|
|
2021-06-22 08:01:27 +00:00
|
|
|
get_weapon(Id, Gun, Vsn, SSLOptions) ->
|
2019-06-06 10:11:48 +00:00
|
|
|
Context = woody_context:new(to_binary(Id)),
|
|
|
|
{Url, Service} = get_service_endpoint('Weapons'),
|
|
|
|
Options = #{
|
|
|
|
url => Url,
|
2021-06-22 08:01:27 +00:00
|
|
|
event_handler => {woody_ct_event_h, {client, Vsn}},
|
2019-06-06 10:11:48 +00:00
|
|
|
transport_opts => #{
|
|
|
|
ssl_options => [
|
|
|
|
{server_name_indication, "Test Server"},
|
2021-01-15 12:35:41 +00:00
|
|
|
{verify, verify_peer}
|
|
|
|
| SSLOptions
|
2019-06-06 10:11:48 +00:00
|
|
|
]
|
|
|
|
}
|
|
|
|
},
|
2020-08-18 10:01:47 +00:00
|
|
|
woody_client:call({Service, get_weapon, {Gun, <<>>}}, Options, Context).
|
2019-06-06 10:11:48 +00:00
|
|
|
|
|
|
|
get_service_endpoint('Weapons') ->
|
|
|
|
{
|
|
|
|
"https://localhost:8043" ?PATH,
|
|
|
|
{?THRIFT_DEFS, 'Weapons'}
|
|
|
|
}.
|
|
|
|
|
|
|
|
to_binary(Atom) when is_atom(Atom) ->
|
2021-01-15 12:35:41 +00:00
|
|
|
erlang:atom_to_binary(Atom, utf8).
|
2019-06-24 16:44:45 +00:00
|
|
|
|
|
|
|
assert_common_name(CNs, Context) ->
|
|
|
|
CN = woody_context:get_common_name(Context),
|
|
|
|
true = lists:member(CN, CNs).
|