diff options
Diffstat (limited to 'lib/ssh/test/ssh_options_SUITE.erl')
-rw-r--r-- | lib/ssh/test/ssh_options_SUITE.erl | 414 |
1 files changed, 347 insertions, 67 deletions
diff --git a/lib/ssh/test/ssh_options_SUITE.erl b/lib/ssh/test/ssh_options_SUITE.erl index 82c8b956c9..c02b20d6a2 100644 --- a/lib/ssh/test/ssh_options_SUITE.erl +++ b/lib/ssh/test/ssh_options_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2021. All Rights Reserved. +%% Copyright Ericsson AB 2008-2022. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ auth_method_kb_interactive_data_tuple/1, auth_method_kb_interactive_data_fun3/1, auth_method_kb_interactive_data_fun4/1, + auth_none/1, connectfun_disconnectfun_client/1, disconnectfun_option_client/1, disconnectfun_option_server/1, @@ -45,6 +46,7 @@ id_string_own_string_server_trail_space/1, id_string_random_client/1, id_string_random_server/1, + max_log_item_len/1, max_sessions_sftp_start_channel_parallel/1, max_sessions_sftp_start_channel_sequential/1, max_sessions_ssh_connect_parallel/1, @@ -82,7 +84,11 @@ save_accepted_host_option/1, raw_option/1, config_file/1, - config_file_modify_algorithms_order/1 + config_file_modify_algorithms_order/1, + daemon_replace_options_simple/1, + daemon_replace_options_algs/1, + daemon_replace_options_algs_connect/1, + daemon_replace_options_algs_conf_file/1 ]). %%% Common test callbacks @@ -92,6 +98,10 @@ init_per_testcase/2, end_per_testcase/2 ]). +%%% For test nodes +-export([get_preferred_algorithms/2 + ]). + -define(NEWLINE, <<"\r\n">>). %%-------------------------------------------------------------------- @@ -114,6 +124,7 @@ all() -> auth_method_kb_interactive_data_tuple, auth_method_kb_interactive_data_fun3, auth_method_kb_interactive_data_fun4, + auth_none, {group, dir_options}, ssh_connect_timeout, ssh_connect_arg4_timeout, @@ -139,10 +150,15 @@ all() -> id_string_own_string_server, id_string_own_string_server_trail_space, id_string_random_server, + max_log_item_len, save_accepted_host_option, raw_option, config_file, config_file_modify_algorithms_order, + daemon_replace_options_simple, + daemon_replace_options_algs, + daemon_replace_options_algs_connect, + daemon_replace_options_algs_conf_file, {group, hardening_tests} ]. @@ -574,6 +590,30 @@ amkid(Config, {ExpectName,ExpectInstr,ExpectPrompts,ExpectEcho}, OptVal) -> {"bar",2}]). %%-------------------------------------------------------------------- +auth_none(Config) -> + PrivDir = proplists:get_value(priv_dir, Config), + UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth + file:make_dir(UserDir), + SysDir = proplists:get_value(data_dir, Config), + {DaemonRef, Host, Port} = + ssh_test_lib:daemon([{system_dir, SysDir}, + {user_dir, UserDir}, + {auth_methods, "password"}, % to make even more sure we don't use public-key-auth + {user_passwords, [{"foo","somepwd"}]}, % Not to be used + {no_auth_needed, true} % we test this + ]), + ClientConnRef1 = + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "some-other-user"}, + {password, "wrong-pwd"}, + {user_dir, UserDir}, + {user_interaction, false}]), + "some-other-user" = + proplists:get_value(user, ssh:connection_info(ClientConnRef1, [user])), + ok = ssh:close(ClientConnRef1), + ok = ssh:stop_daemon(DaemonRef). + +%%-------------------------------------------------------------------- system_dir_option(Config) -> DirUnread = proplists:get_value(unreadable_dir,Config), FileRead = proplists:get_value(readable_file,Config), @@ -1352,6 +1392,67 @@ one_shell_op(IO, TimeOut) -> end. %%-------------------------------------------------------------------- +max_log_item_len(Config) -> + %% Find a supported algorithm (to be removed from the daemon): + {ok, {Type,Alg}} = select_alg( ssh:default_algorithms() ), + + %% Start a test daemon without support for {Type,Alg} + SystemDir = proplists:get_value(data_dir, Config), + UserDir = proplists:get_value(priv_dir, Config), + {_Pid, Host0, Port} = + ssh_test_lib:daemon([ + {system_dir, SystemDir}, + {user_dir, UserDir}, + {user_passwords, [{"carni", "meat"}]}, + {modify_algorithms, [{rm, [{Type,[Alg]}]}]}, + {max_log_item_len, 10} + ]), + Host = ssh_test_lib:mangle_connect_address(Host0), + ct:log("~p:~p Listen ~p:~p. Mangled Host = ~p", + [?MODULE,?LINE,Host0,Port,Host]), + + {ok,ReportHandlerPid} = ssh_eqc_event_handler:add_report_handler(), + + %% Connect to it with the {Type,Alg} to force a failure and log entry: + {error,_} = R = + ssh:connect(Host, Port, + [{preferred_algorithms, [{Type,[Alg]}]}, + {max_log_item_len, 10}, + {silently_accept_hosts, true}, + {save_accepted_host, false}, + {user_dir, UserDir}, + {user_interaction, false}, + {user, "carni"}, + {password, "meat"} + ]), + + {ok, Reports} = ssh_eqc_event_handler:get_reports(ReportHandlerPid), + ct:log("~p:~p ssh:connect -> ~p~n~p", [?MODULE,?LINE,R,Reports]), + + [ok] = + lists:usort( + [check_skip_part( + string:tokens( + lists:flatten(io_lib:format(Fmt,Args)), + " \n")) + || {info_msg,_,{_,Fmt,Args}} <- Reports] + ). + + +check_skip_part(["Disconnect","...","("++_NumSkipped, "bytes","skipped)"]) -> + ok; +check_skip_part([_|T]) -> + check_skip_part(T); +check_skip_part([]) -> + error. + +select_alg([{Type,[A,_|_]}|_]) when is_atom(A) -> {ok, {Type,A}}; +select_alg([{Type,[{Dir,[A,_|_]}, _]}|_]) when is_atom(A), is_atom(Dir) -> {ok, {Type,A}}; +select_alg([{Type,[_,{Dir,[A,_|_]}]}|_]) when is_atom(A), is_atom(Dir) -> {ok, {Type,A}}; +select_alg([_|Algs]) -> select_alg(Algs); +select_alg([]) -> false. + +%%-------------------------------------------------------------------- max_sessions_ssh_connect_parallel(Config) -> max_sessions(Config, true, connect_fun(ssh__connect,Config)). max_sessions_ssh_connect_sequential(Config) -> @@ -1412,7 +1513,7 @@ max_sessions(Config, ParallelLogin, Connect0) when is_function(Connect0,2) -> ct:log("Connections up: ~p",[Connections]), [_|_] = Connections, - %% N w try one more than alowed: + %% N w try one more than allowed: ct:pal("Info Report expected here (if not disabled) ...",[]), try Connect(Host,Port) of @@ -1442,7 +1543,7 @@ try_to_connect(Connect, Host, Port, Pid, Tref, N) -> of _ConnectionRef1 -> timer:cancel(Tref), - ct:log("Step 3 ok: could set up one more connection after killing one. Thats good.",[]), + ct:log("Step 3 ok: could set up one more connection after killing one. That's good.",[]), ssh:stop_daemon(Pid), receive % flush. timeout_no_connection -> ok @@ -1611,30 +1712,24 @@ config_file(Config) -> [{_,[Ch1|_]}|_] = proplists:get_value(cipher, CommonAlgs), %% Make config file: - Contents = - [{ssh, [{preferred_algorithms, - [{cipher, [Ch1]}, - {kex, [K1a]} - ] ++ AdjustClient}, - {client_options, - [{modify_algorithms, - [{rm, [{kex, [K1a]}]}, - {append, [{kex, [K1b]}]} + {ok,ConfFile} = + make_config_file_in_privdir( + "c2.config", Config, + [{ssh, [{preferred_algorithms, + [{cipher, [Ch1]}, + {kex, [K1a]} + ] ++ AdjustClient}, + {client_options, + [{modify_algorithms, + [{rm, [{kex, [K1a]}]}, + {append, [{kex, [K1b]}]} + ]} ]} ]} - ]} - ], - %% write the file: - PrivDir = proplists:get_value(priv_dir, Config), - ConfFile = filename:join(PrivDir,"c2.config"), - {ok,D} = file:open(ConfFile, [write]), - io:format(D, "~p.~n", [Contents]), - file:close(D), - {ok,Cnfs} = file:read_file(ConfFile), - ct:log("c2.config:~n~s", [Cnfs]), + ]), %% Start the slave node with the configuration just made: - {ok,Node} = start_node(random_node_name(?MODULE), ConfFile), + {ok, Peer, Node} = ?CT_PEER(["-config", ConfFile]), R0 = rpc:call(Node, ssh, default_algorithms, []), ct:log("R0 = ~p",[R0]), @@ -1679,7 +1774,7 @@ config_file(Config) -> {options,Os2} = rpc:call(Node, ssh, connection_info, [C2, options]), ct:log("C2 opts:~n~p~n~nalgorithms:~n~p~n~noptions:~n~p", [C2_Opts,As2,Os2]), - stop_node_nice(Node) + peer:stop(Peer) end. %%%---------------------------------------------------------------- @@ -1703,36 +1798,30 @@ config_file_modify_algorithms_order(Config) -> [{_,[Ch1|_]}|_] = proplists:get_value(cipher, CommonAlgs), %% Make config file: - Contents = - [{ssh, [{preferred_algorithms, - [{cipher, [Ch1]}, - {kex, [K1]} - ]}, - {server_options, - [{modify_algorithms, - [{rm, [{kex, [K1]}]}, - {append, [{kex, [K2]}]} - ]} - ]}, - {client_options, - [{modify_algorithms, - [{rm, [{kex, [K1]}]}, - {append, [{kex, [K3]}]} + {ok, ConfFile} = + make_config_file_in_privdir( + "c3.config", Config, + [{ssh, [{preferred_algorithms, + [{cipher, [Ch1]}, + {kex, [K1]} + ]}, + {server_options, + [{modify_algorithms, + [{rm, [{kex, [K1]}]}, + {append, [{kex, [K2]}]} + ]} + ]}, + {client_options, + [{modify_algorithms, + [{rm, [{kex, [K1]}]}, + {append, [{kex, [K3]}]} + ]} ]} ]} - ]} - ], - %% write the file: - PrivDir = proplists:get_value(priv_dir, Config), - ConfFile = filename:join(PrivDir,"c3.config"), - {ok,D} = file:open(ConfFile, [write]), - io:format(D, "~p.~n", [Contents]), - file:close(D), - {ok,Cnfs} = file:read_file(ConfFile), - ct:log("c3.config:~n~s", [Cnfs]), + ]), %% Start the slave node with the configuration just made: - {ok,Node} = start_node(random_node_name(?MODULE), ConfFile), + {ok, Peer, Node} = ?CT_PEER(["-config", ConfFile]), R0 = rpc:call(Node, ssh, default_algorithms, []), ct:log("R0 = ~p",[R0]), @@ -1769,32 +1858,184 @@ config_file_modify_algorithms_order(Config) -> ConnOptions = proplists:get_value(options, ConnInfo), ConnPrefAlgs = proplists:get_value(preferred_algorithms, ConnOptions), - %% And now, are all levels appied in right order: + %% And now, are all levels applied in right order: [K3,K2] = proplists:get_value(kex, ConnPrefAlgs), - stop_node_nice(Node) + peer:stop(Peer) end. %%-------------------------------------------------------------------- -%% Internal functions ------------------------------------------------ +daemon_replace_options_simple(Config) -> + SysDir = proplists:get_value(data_dir, Config), + + UserDir1 = proplists:get_value(user_dir, Config), + UserDir2 = filename:join(UserDir1, "foo"), + file:make_dir(UserDir2), + + {Pid, _Host, _Port} = ssh_test_lib:daemon([{system_dir, SysDir}, + {user_dir, UserDir1} + ]), + {ok,Opts1} = ssh:daemon_info(Pid), + UserDir1 = proplists:get_value(user_dir, proplists:get_value(options,Opts1,[])), + + {ok, Pid} = ssh:daemon_replace_options(Pid, [{user_dir,UserDir2}]), + {ok,Opts2} = ssh:daemon_info(Pid), + case proplists:get_value(user_dir, proplists:get_value(options,Opts2,[])) of + UserDir2 -> + ok; + UserDir1 -> + ct:log("~p:~p Got old value ~p~nExpected ~p", [?MODULE,?LINE,UserDir1,UserDir2]), + {fail, "Not changed"}; + Other -> + ct:log("~p:~p Got ~p~nExpected ~p", [?MODULE,?LINE,Other,UserDir2]), + {fail, "Strange value"} + end. + +%%-------------------------------------------------------------------- +daemon_replace_options_algs(Config) -> + SysDir = proplists:get_value(data_dir, Config), + UserDir = proplists:get_value(user_dir, Config), + + DefaultKex = + ssh_transport:default_algorithms(kex), + NonDefaultKex = + ssh_transport:supported_algorithms(kex) -- DefaultKex, + + case NonDefaultKex of + [A1|_] -> + [A2,A3|_] = DefaultKex, + {Pid, _Host, _Port} = ssh_test_lib:daemon([{system_dir, SysDir}, + {user_dir, UserDir}, + {preferred_algorithms,[{kex,[A1]}]} + ]), + [A1] = get_preferred_algorithms(Pid, kex), + {ok, Pid} = + ssh:daemon_replace_options(Pid, [{modify_algorithms, + [{prepend,[{kex,[A2]}]}] + } + ]), + [A2,A1] = get_preferred_algorithms(Pid, kex), + + {ok, Pid} = + ssh:daemon_replace_options(Pid, [{preferred_algorithms,[{kex,[A3]}] + } + ]), + [A2,A3] = get_preferred_algorithms(Pid, kex) + ; + [] -> + {skip, "No non-default kex"} + end. + %%-------------------------------------------------------------------- +daemon_replace_options_algs_connect(Config) -> + [A1,A2|_] = + ssh_transport:default_algorithms(kex), + + {Pid, Host, Port} = + ssh_test_lib:std_daemon(Config, + [{preferred_algorithms,[{kex,[A1]}]} + ]), + [A1] = get_preferred_algorithms(Pid, kex), + + %% Open a connection with A1 as kex and test it + C1 = + ssh_test_lib:std_connect(Config, Host, Port, + [{preferred_algorithms,[{kex,[A1]}]} + ]), + ok = test_connection(C1), + ok = test_not_connect(Config, Host, Port, + [{preferred_algorithms,[{kex,[A2]}]} + ]), + + %% Change kex to A2 + {ok, Pid} = + ssh:daemon_replace_options(Pid, + [{preferred_algorithms,[{kex,[A2]}]}]), + [A2] = get_preferred_algorithms(Pid, kex), + + %% and open the second connection with this kex, and test it + C2 = + ssh_test_lib:std_connect(Config, Host, Port, + [{preferred_algorithms,[{kex,[A2]}]} + ]), + ok = test_connection(C2), + ok = test_not_connect(Config, Host, Port, + [{preferred_algorithms,[{kex,[A1]}]} + ]), + + %% Test that the first connection is still alive: + ok = test_connection(C1), + + ssh:close(C1), + ssh:close(C2), + ssh:stop_daemon(Pid). -start_node(Name, ConfigFile) -> - Pa = filename:dirname(code:which(?MODULE)), - test_server:start_node(Name, slave, [{args, - " -pa " ++ Pa ++ - " -config " ++ ConfigFile}]). +%%-------------------------------------------------------------------- +daemon_replace_options_algs_conf_file(Config) -> + SysDir = proplists:get_value(data_dir, Config), + UserDir = proplists:get_value(user_dir, Config), -stop_node_nice(Node) when is_atom(Node) -> - test_server:stop_node(Node). + DefaultKex = + ssh_transport:default_algorithms(kex), + NonDefaultKex = + ssh_transport:supported_algorithms(kex) -- DefaultKex, -random_node_name(BaseName) -> - L = integer_to_list(erlang:unique_integer([positive])), - lists:concat([BaseName,"___",L]). + case NonDefaultKex of + [A0,A1|_] -> + %% Make config file: + {ok,ConfFile} = + make_config_file_in_privdir( + "c4.config", Config, + [{ssh, [{modify_algorithms, + %% Whatever happens, always put A0 first in the kex list: + [{prepend, [{kex, [A0]}]} + ]} + ]} + ]), + + [A2|_] = DefaultKex, + ct:log("[A0, A1, A2] = ~p", [[A0, A1, A2]]), + + %% Start the slave node with the configuration just made: + {ok, Peer, Node} = ?CT_PEER(["-config", ConfFile]), + + %% Start ssh on the slave. This should apply the ConfFile: + rpc:call(Node, ssh, start, []), + + {Pid, _Host, _Port} = + rpc:call(Node, ssh_test_lib, daemon, + [ + [{system_dir, SysDir}, + {user_dir, UserDir}, + {preferred_algorithms,[{kex,[A1]}]} + ] + ]), + + [A0,A1] = + rpc:call(Node, ?MODULE, get_preferred_algorithms, [Pid, kex]), + {ok, Pid} = + rpc:call(Node, ssh, daemon_replace_options, + [Pid, + [{modify_algorithms, + [{prepend,[{kex,[A2]}]}] + } + ] + ]), + + %% Check that the precedens order is fulfilled: + [A2,A0,A1] = + rpc:call(Node, ?MODULE, get_preferred_algorithms, [Pid, kex]), + + peer:stop(Peer); + [] -> + {skip, "No non-default kex"} + end. + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- -%%%---- - expected_ssh_vsn(Str) -> try {ok,L} = application:get_all_key(ssh), @@ -1804,7 +2045,7 @@ expected_ssh_vsn(Str) -> "\r\n" -> true; _ -> false catch - _:_ -> true %% ssh not started so we dont't know + _:_ -> true %% ssh not started so we don't know end. @@ -1831,3 +2072,42 @@ fake_daemon(_Config) -> after 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. + + +make_config_file_in_privdir(FileName, Config, Contents) -> + %% write the file: + PrivDir = proplists:get_value(priv_dir, Config), + ConfFile = filename:join(PrivDir, FileName), + {ok,D} = file:open(ConfFile, [write]), + io:format(D, "~p.~n", [Contents]), + file:close(D), + {ok,Cnfs} = file:read_file(ConfFile), + ct:log("Config file ~p :~n~s", [ConfFile,Cnfs]), + {ok,ConfFile}. + + +get_preferred_algorithms(Pid, Type) -> + {ok,#{preferred_algorithms:=As}} = ssh_system_sup:get_acceptor_options(Pid), + proplists:get_value(Type, As). + +test_connection(C) -> + {ok, Ch} = ssh_connection:session_channel(C, infinity), + A = rand:uniform(100), + B = rand:uniform(100), + A_plus_B = lists:concat([A,"+",B,"."]), + Sum = integer_to_binary(A+B), + success = ssh_connection:exec(C, Ch, A_plus_B, infinity), + expected = ssh_test_lib:receive_exec_result( + {ssh_cm, C, {data, Ch, 0, Sum}} ), + ssh_test_lib:receive_exec_end(C, Ch), + ok. + +test_not_connect(Config, Host, Port, Opts) -> + try + ssh_test_lib:std_connect(Config, Host, Port, Opts) + of + Cx when is_pid(Cx) -> {error, connected} + catch + error:{badmatch, {error,_}} -> ok + end. + |