Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package crmsh for openSUSE:Factory checked in at 2023-04-27 20:00:36 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/crmsh (Old) and /work/SRC/openSUSE:Factory/.crmsh.new.1533 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "crmsh" Thu Apr 27 20:00:36 2023 rev:293 rq:1083164 version:4.5.0+20230427.11d11104 Changes: -------- --- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes 2023-04-24 22:31:53.791754332 +0200 +++ /work/SRC/openSUSE:Factory/.crmsh.new.1533/crmsh.changes 2023-04-27 20:00:54.141914195 +0200 @@ -1,0 +2,15 @@ +Thu Apr 27 06:35:41 UTC 2023 - xli...@suse.com + +- Update to version 4.5.0+20230427.11d11104: + * Dev: behave: Add <user>@ when joining for non-root case + * Dev: behave: Add functional test for bsc#1210693 + +------------------------------------------------------------------- +Wed Apr 26 14:28:37 UTC 2023 - xli...@suse.com + +- Update to version 4.5.0+20230426.b7c4b1aa: + * Dev: behave: Adjust functional test for previous change + * Dev: unittest: adjust unit tests for previous changes + * Fix: bootstrap: crm cluster join default behavior change in ssh key handling (bsc#1210693) + +------------------------------------------------------------------- Old: ---- crmsh-4.5.0+20230424.75600b3f.tar.bz2 New: ---- crmsh-4.5.0+20230427.11d11104.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ crmsh.spec ++++++ --- /var/tmp/diff_new_pack.qUF42E/_old 2023-04-27 20:00:54.701917487 +0200 +++ /var/tmp/diff_new_pack.qUF42E/_new 2023-04-27 20:00:54.705917510 +0200 @@ -36,7 +36,7 @@ Summary: High Availability cluster command-line interface License: GPL-2.0-or-later Group: %{pkg_group} -Version: 4.5.0+20230424.75600b3f +Version: 4.5.0+20230427.11d11104 Release: 0 URL: http://crmsh.github.io Source0: %{name}-%{version}.tar.bz2 ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.qUF42E/_old 2023-04-27 20:00:54.749917768 +0200 +++ /var/tmp/diff_new_pack.qUF42E/_new 2023-04-27 20:00:54.753917792 +0200 @@ -9,7 +9,7 @@ </service> <service name="tar_scm"> <param name="url">https://github.com/ClusterLabs/crmsh.git</param> - <param name="changesrevision">75600b3f641bdcc21b2252080700e71d17e1d03a</param> + <param name="changesrevision">03886aa3a42cdc817f65bcf88eaffc75a4d6845d</param> </service> </servicedata> (No newline at EOF) ++++++ crmsh-4.5.0+20230424.75600b3f.tar.bz2 -> crmsh-4.5.0+20230427.11d11104.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.5.0+20230424.75600b3f/crmsh/bootstrap.py new/crmsh-4.5.0+20230427.11d11104/crmsh/bootstrap.py --- old/crmsh-4.5.0+20230424.75600b3f/crmsh/bootstrap.py 2023-04-24 10:59:23.000000000 +0200 +++ new/crmsh-4.5.0+20230427.11d11104/crmsh/bootstrap.py 2023-04-27 08:13:57.000000000 +0200 @@ -187,17 +187,11 @@ else: utils.fatal("Unsupported config: local node is using root and remote nodes is using non-root users.") elif users_of_specified_hosts == 'not_specified': - if has_sudoer: - self.current_user = sudoer - else: - assert userdir.getuser() == 'root' - self.current_user = 'root' + assert userdir.getuser() == 'root' + self.current_user = 'root' elif users_of_specified_hosts == 'no_hosts': - if has_sudoer: - self.current_user = sudoer - else: - assert userdir.getuser() == 'root' - self.current_user = 'root' + assert userdir.getuser() == 'root' + self.current_user = 'root' else: raise AssertionError('Bad parameter user_of_specified_hosts: {}'.format(users_of_specified_hosts)) @@ -872,9 +866,6 @@ # If not use -N/--nodes option if not node_list: - user_by_host = utils.HostUserConfig() - user_by_host.add(local_user, utils.this_node()) - user_by_host.save_local() return print() @@ -1635,22 +1626,31 @@ if qdevice_inst.ssh_user is not None: # if the remote user is specified explicitly, use it ssh_user = qdevice_inst.ssh_user + local_user = utils.UserOfHost.instance().user_of(utils.this_node()) else: try: # if ssh session has ready been available, use that local_user, ssh_user = utils.UserOfHost.instance().user_pair_for_ssh(qnetd_addr) except utils.UserOfHost.UserNotFoundError: pass - if local_user is None: - local_user = userdir.get_sudoer() - if local_user is None: - local_user = userdir.getuser() if ssh_user is None: + local_user = userdir.get_sudoer() ssh_user = local_user # Configure ssh passwordless to qnetd if detect password is needed if utils.check_ssh_passwd_need(local_user, ssh_user, qnetd_addr): configure_ssh_key(local_user) - utils.ssh_copy_id(local_user, ssh_user, qnetd_addr) + if 0 != utils.ssh_copy_id_no_raise(local_user, ssh_user, qnetd_addr): + msg = f"Failed to login to {ssh_user}@{qnetd_addr}. Please check the credentials." + sudoer = userdir.get_sudoer() + if sudoer and ssh_user != sudoer: + args = ['sudo crm'] + args += [x for x in sys.argv[1:]] + for i, arg in enumerate(args): + if arg == '--qnetd-hostname' and i + 1 < len(args): + if '@' not in args[i + 1]: + args[i + 1] = f'{sudoer}@{qnetd_addr}' + msg += '\nOr, run "{}".'.format(' '.join(args)) + raise ValueError(msg) user_by_host = utils.HostUserConfig() user_by_host.add(ssh_user, qnetd_addr) user_by_host.save_remote(cluster_node_list) @@ -1692,7 +1692,18 @@ local_user = _context.current_user utils.start_service("sshd.service", enable=True) configure_ssh_key(local_user) - utils.ssh_copy_id(local_user, seed_user, seed_host) + if 0 != utils.ssh_copy_id_no_raise(local_user, seed_user, seed_host): + msg = f"Failed to login to {seed_user}@{seed_host}. Please check the credentials." + sudoer = userdir.get_sudoer() + if sudoer and seed_user != sudoer: + args = ['sudo crm'] + args += [x for x in sys.argv[1:]] + for i, arg in enumerate(args): + if arg == '-c' or arg == '--cluster-node' and i + 1 < len(args): + if '@' not in args[i+1]: + args[i + 1] = f'{sudoer}@{seed_host}' + msg += '\nOr, run "{}".'.format(' '.join(args)) + raise ValueError(msg) # After this, login to remote_node is passwordless swap_public_ssh_key(seed_host, local_user, seed_user, local_user, seed_user, add=True) configure_ssh_key('hacluster') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.5.0+20230424.75600b3f/crmsh/ui_cluster.py new/crmsh-4.5.0+20230427.11d11104/crmsh/ui_cluster.py --- old/crmsh-4.5.0+20230424.75600b3f/crmsh/ui_cluster.py 2023-04-24 10:59:23.000000000 +0200 +++ new/crmsh-4.5.0+20230427.11d11104/crmsh/ui_cluster.py 2023-04-27 08:13:57.000000000 +0200 @@ -317,8 +317,8 @@ help="Configure corosync use IPv6") qdevice_group = parser.add_argument_group("QDevice configuration", re.sub(' ', '', constants.QDEVICE_HELP_INFO) + "\n\nOptions for configuring QDevice and QNetd.") - qdevice_group.add_argument("--qnetd-hostname", dest="qnetd_addr", metavar="HOST", - help="HOST or IP of the QNetd server to be used") + qdevice_group.add_argument("--qnetd-hostname", dest="qnetd_addr", metavar="[USER@]HOST", + help="User and host of the QNetd server. The host can be specified in either hostname or IP address.") qdevice_group.add_argument("--qdevice-port", dest="qdevice_port", metavar="PORT", type=int, default=5403, help="TCP PORT of QNetd server (default:5403)") qdevice_group.add_argument("--qdevice-algo", dest="qdevice_algo", metavar="ALGORITHM", default="ffsplit", choices=['ffsplit', 'lms'], @@ -413,7 +413,10 @@ parser.add_argument("-w", "--watchdog", dest="watchdog", metavar="WATCHDOG", help="Use the given watchdog device") network_group = parser.add_argument_group("Network configuration", "Options for configuring the network and messaging layer.") - network_group.add_argument("-c", "--cluster-node", dest="cluster_node", help="IP address or hostname of existing cluster node", metavar="HOST") + network_group.add_argument( + "-c", "--cluster-node", dest="cluster_node", metavar="[USER@]HOST", + help="User and host to login to an existing cluster node. The host can be specified with either a hostname or an IP.", + ) network_group.add_argument("-i", "--interface", dest="nic_list", metavar="IF", action="append", choices=utils.interface_choice(), default=[], help="Bind to IP address on interface IF. Use -i second time for second interface") options, args = parse_options(parser, args) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.5.0+20230424.75600b3f/crmsh/utils.py new/crmsh-4.5.0+20230427.11d11104/crmsh/utils.py --- old/crmsh-4.5.0+20230424.75600b3f/crmsh/utils.py 2023-04-24 10:59:23.000000000 +0200 +++ new/crmsh-4.5.0+20230427.11d11104/crmsh/utils.py 2023-04-27 08:13:57.000000000 +0200 @@ -225,13 +225,19 @@ raise ValueError('Can not create ssh session from {} to {}.'.format(this_node(), host)) -def ssh_copy_id(local_user, remote_user, remote_node): +def ssh_copy_id_no_raise(local_user, remote_user, remote_node): if check_ssh_passwd_need(local_user, remote_user, remote_node): logger.info("Configuring SSH passwordless with {}@{}".format(remote_user, remote_node)) cmd = "ssh-copy-id -i ~/.ssh/id_rsa.pub '{}@{}' &> /dev/null".format(remote_user, remote_node) result = su_subprocess_run(local_user, cmd, tty=True) - if result.returncode != 0: - fatal("Failed to login to remote host {}@{}".format(remote_user, remote_node)) + return result.returncode + else: + return 0 + + +def ssh_copy_id(local_user, remote_user, remote_node): + if 0 != ssh_copy_id_no_raise(local_user, remote_user, remote_node): + fatal("Failed to login to remote host {}@{}".format(remote_user, remote_node)) @memoize diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.5.0+20230424.75600b3f/test/features/bootstrap_bugs.feature new/crmsh-4.5.0+20230427.11d11104/test/features/bootstrap_bugs.feature --- old/crmsh-4.5.0+20230424.75600b3f/test/features/bootstrap_bugs.feature 2023-04-24 10:59:23.000000000 +0200 +++ new/crmsh-4.5.0+20230427.11d11104/test/features/bootstrap_bugs.feature 2023-04-27 08:13:57.000000000 +0200 @@ -139,6 +139,18 @@ @skip_non_root @clean + Scenario: crm cluster join default behavior change in ssh key handling (bsc#1210693) + Given Cluster service is "stopped" on "hanode1" + Given Cluster service is "stopped" on "hanode2" + When Run "rm -rf /home/alice/.ssh" on "hanode1" + When Run "rm -rf /home/alice/.ssh" on "hanode2" + When Run "su - alice -c "sudo crm cluster init -y"" on "hanode1" + Then Cluster service is "started" on "hanode1" + When Run "su - alice -c "sudo crm cluster join -c hanode1 -y"" on "hanode2" + Then Cluster service is "started" on "hanode2" + + @skip_non_root + @clean Scenario: Passwordless for root, not for sudoer(bsc#1209193) Given Cluster service is "stopped" on "hanode1" And Cluster service is "stopped" on "hanode2" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.5.0+20230424.75600b3f/test/features/steps/const.py new/crmsh-4.5.0+20230427.11d11104/test/features/steps/const.py --- old/crmsh-4.5.0+20230424.75600b3f/test/features/steps/const.py 2023-04-24 10:59:23.000000000 +0200 +++ new/crmsh-4.5.0+20230427.11d11104/test/features/steps/const.py 2023-04-27 08:13:57.000000000 +0200 @@ -107,8 +107,9 @@ Options for configuring QDevice and QNetd. - --qnetd-hostname HOST - HOST or IP of the QNetd server to be used + --qnetd-hostname [USER@]HOST + User and host of the QNetd server. The host can be + specified in either hostname or IP address. --qdevice-port PORT TCP PORT of QNetd server (default:5403) --qdevice-algo ALGORITHM QNetd decision ALGORITHM (ffsplit/lms, @@ -223,8 +224,10 @@ Network configuration: Options for configuring the network and messaging layer. - -c HOST, --cluster-node HOST - IP address or hostname of existing cluster node + -c [USER@]HOST, --cluster-node [USER@]HOST + User and host to login to an existing cluster node. + The host can be specified with either a hostname or an + IP. -i IF, --interface IF Bind to IP address on interface IF. Use -i second time for second interface diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.5.0+20230424.75600b3f/test/features/steps/utils.py new/crmsh-4.5.0+20230427.11d11104/test/features/steps/utils.py --- old/crmsh-4.5.0+20230424.75600b3f/test/features/steps/utils.py 2023-04-24 10:59:23.000000000 +0200 +++ new/crmsh-4.5.0+20230427.11d11104/test/features/steps/utils.py 2023-04-27 08:13:57.000000000 +0200 @@ -4,7 +4,7 @@ import glob import re import socket -from crmsh import utils, bootstrap, parallax +from crmsh import utils, bootstrap, parallax, userdir COLOR_MODE = r'\x1b\[[0-9]+m' @@ -46,7 +46,28 @@ return cmd +def _wrap_cmd_non_root(cmd): + """ + When running command under sudoer, or the current user is not root, + wrap crm cluster join command with '<user>@' + """ + user = "" + sudoer = userdir.get_sudoer() + current_user = userdir.getuser() + if sudoer: + user = sudoer + elif current_user != 'root': + user = current_user + else: + return cmd + if "cluster join" in cmd and "@" not in cmd: + return re.sub("-c (\w+) ", f"-c {user}@\\1 ", cmd) + else: + return cmd + + def run_command(context, cmd, exit_on_fail=True): + cmd = _wrap_cmd_non_root(cmd) rc, out, err = utils.get_stdout_stderr(add_sudo(cmd)) context.return_code = rc if out: @@ -68,6 +89,7 @@ if addr == me(): return run_command(context, cmd, exit_on_fail) else: + cmd = _wrap_cmd_non_root(cmd) try: results = parallax.parallax_call(addr.split(','), cmd) except ValueError as err: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-4.5.0+20230424.75600b3f/test/unittests/test_bootstrap.py new/crmsh-4.5.0+20230427.11d11104/test/unittests/test_bootstrap.py --- old/crmsh-4.5.0+20230424.75600b3f/test/unittests/test_bootstrap.py 2023-04-24 10:59:23.000000000 +0200 +++ new/crmsh-4.5.0+20230427.11d11104/test/unittests/test_bootstrap.py 2023-04-27 08:13:57.000000000 +0200 @@ -452,13 +452,14 @@ @mock.patch('crmsh.bootstrap.change_user_shell') @mock.patch('crmsh.utils.su_get_stdout_or_raise_error') @mock.patch('crmsh.bootstrap.swap_public_ssh_key') - @mock.patch('crmsh.utils.ssh_copy_id') + @mock.patch('crmsh.utils.ssh_copy_id_no_raise') @mock.patch('crmsh.bootstrap.configure_ssh_key') @mock.patch('crmsh.utils.start_service') def test_join_ssh(self, mock_start_service, mock_config_ssh, mock_ssh_copy_id, mock_swap, mock_invoke, mock_change): bootstrap._context = mock.Mock(current_user="bob", user_list=["alice"], node_list=['node1'], default_nic_list=["eth1"]) mock_invoke.return_value = '' mock_swap.return_value = None + mock_ssh_copy_id.return_value = 0 bootstrap.join_ssh_impl("node1", "alice") @@ -477,6 +478,30 @@ "bob", ) + @mock.patch('crmsh.bootstrap.change_user_shell') + @mock.patch('crmsh.utils.su_get_stdout_or_raise_error') + @mock.patch('crmsh.bootstrap.swap_public_ssh_key') + @mock.patch('crmsh.utils.ssh_copy_id_no_raise') + @mock.patch('crmsh.bootstrap.configure_ssh_key') + @mock.patch('crmsh.utils.start_service') + def test_join_ssh_bad_credential(self, mock_start_service, mock_config_ssh, mock_ssh_copy_id, mock_swap, mock_invoke, mock_change): + bootstrap._context = mock.Mock(current_user="bob", user_list=["alice"], node_list=['node1'], default_nic_list=["eth1"]) + mock_invoke.return_value = '' + mock_swap.return_value = None + mock_ssh_copy_id.return_value = 255 + + with self.assertRaises(ValueError): + bootstrap.join_ssh_impl("node1", "alice") + + mock_start_service.assert_called_once_with("sshd.service", enable=True) + mock_config_ssh.assert_has_calls([ + mock.call("bob"), + ]) + mock_ssh_copy_id.assert_called_once_with("bob", "alice", "node1") + mock_swap.assert_not_called() + mock_invoke.assert_not_called() + + @mock.patch('crmsh.bootstrap.import_ssh_key') @mock.patch('crmsh.bootstrap.export_ssh_key_non_interactive') @mock.patch('logging.Logger.warning') @@ -801,7 +826,7 @@ @mock.patch('crmsh.utils.HostUserConfig') @mock.patch('crmsh.utils.UserOfHost.instance') @mock.patch('crmsh.utils.list_cluster_nodes') - @mock.patch('crmsh.utils.ssh_copy_id') + @mock.patch('crmsh.utils.ssh_copy_id_no_raise') @mock.patch('crmsh.bootstrap.configure_ssh_key') @mock.patch('crmsh.utils.check_ssh_passwd_need') @mock.patch('logging.Logger.info') @@ -814,7 +839,7 @@ mock_list_nodes.return_value = [] bootstrap._context = mock.Mock(qdevice_inst=self.qdevice_with_ip, current_user="bob", user_list=["alice"]) mock_check_ssh_passwd_need.return_value = True - mock_ssh_copy_id.side_effect = ValueError('foo') + mock_ssh_copy_id.return_value = 255 mock_user_of_host.return_value = mock.MagicMock(crmsh.utils.UserOfHost) mock_user_of_host.return_value.user_pair_for_ssh.return_value = "bob", "bob"