def test_handle_invalid_ssh_keys_are_skipped( self, m_write_file, m_nug, m_setup_keys, key_type, reason, caplog, ): cfg = { "ssh_keys": { f"{key_type}_private": f"{key_type}_private", f"{key_type}_public": f"{key_type}_public", f"{key_type}_certificate": f"{key_type}_certificate", }, "ssh_deletekeys": False, "ssh_publish_hostkeys": { "enabled": False }, } # Run the handler. m_nug.return_value = ([], {}) with mock.patch(MODPATH + "ssh_util.parse_ssh_config", return_value=[]): cc_ssh.handle("name", cfg, get_cloud("ubuntu"), LOG, None) assert [] == m_write_file.call_args_list expected_log_msgs = [ f'Skipping {reason} ssh_keys entry: "{key_type}_private"', f'Skipping {reason} ssh_keys entry: "{key_type}_public"', f'Skipping {reason} ssh_keys entry: "{key_type}_certificate"', ] for expected_log_msg in expected_log_msgs: assert caplog.text.count(expected_log_msg) == 1
def test_handle_default_root( self, m_path_exists, m_nug, m_glob, m_setup_keys, cfg, mock_get_public_ssh_keys, empty_opts, ): """Test handle with a default distro user.""" keys = ["key1"] user = "******" m_glob.return_value = [] # Return no matching keys to prevent removal # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) cloud = get_cloud(distro="ubuntu", metadata={"public-keys": keys}) if mock_get_public_ssh_keys: cloud.get_public_ssh_keys = mock.Mock(return_value=keys) cc_ssh.handle("name", cfg, cloud, LOG, None) if empty_opts: options = "" else: options = _replace_options(user) assert [ mock.call(set(keys), user), mock.call(set(keys), "root", options=options), ] == m_setup_keys.call_args_list
def test_handle_publish_hostkeys_empty_blacklist( self, m_path_exists, m_nug, m_glob, m_setup_keys ): """Test handle with various configs for ssh_publish_hostkeys.""" self._publish_hostkey_test_setup() cc_ssh.PUBLISH_HOST_KEYS = True keys = ["key1"] user = "******" # Return no matching keys for first glob, test keys for second. m_glob.side_effect = iter( [ [], self.test_hostkey_files, ] ) # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) cloud = self.tmp_cloud(distro="ubuntu", metadata={"public-keys": keys}) cloud.datasource.publish_host_keys = mock.Mock() cfg = {"ssh_publish_hostkeys": {"enabled": True, "blacklist": []}} expected_call = [ self.test_hostkeys[key_type] for key_type in cc_ssh.GENERATE_KEY_NAMES ] cc_ssh.handle("name", cfg, cloud, LOG, None) self.assertEqual( [mock.call(expected_call)], cloud.datasource.publish_host_keys.call_args_list, )
def test_handle_cfg_with_explicit_disable_root( self, m_path_exists, m_nug, m_glob, m_setup_keys ): """Test handle with explicit disable_root and a default distro user.""" # This test is identical to test_handle_no_cfg_and_default_root, # except this uses an explicit cfg value cfg = {"disable_root": True} keys = ["key1"] user = "******" m_glob.return_value = [] # Return no matching keys to prevent removal # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) cloud = self.tmp_cloud(distro="ubuntu", metadata={"public-keys": keys}) cc_ssh.handle("name", cfg, cloud, LOG, None) options = ssh_util.DISABLE_USER_OPTS.replace("$USER", user) options = options.replace("$DISABLE_USER", "root") self.assertEqual( [ mock.call(set(keys), user), mock.call(set(keys), "root", options=options), ], m_setup_keys.call_args_list, )
def test_handle_publish_hostkeys_config_disable( self, m_path_exists, m_nug, m_glob, m_setup_keys ): """Test handle with various configs for ssh_publish_hostkeys.""" self._publish_hostkey_test_setup() cc_ssh.PUBLISH_HOST_KEYS = True keys = ["key1"] user = "******" # Return no matching keys for first glob, test keys for second. m_glob.side_effect = iter( [ [], self.test_hostkey_files, ] ) # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) cloud = self.tmp_cloud(distro="ubuntu", metadata={"public-keys": keys}) cloud.datasource.publish_host_keys = mock.Mock() cfg = {"ssh_publish_hostkeys": {"enabled": False}} cc_ssh.handle("name", cfg, cloud, LOG, None) self.assertFalse(cloud.datasource.publish_host_keys.call_args_list) cloud.datasource.publish_host_keys.assert_not_called()
def test_dont_allow_public_ssh_keys( self, m_path_exists, m_nug, m_glob, m_setup_keys ): """Test allow_public_ssh_keys=False ignores ssh public keys from platform. """ cfg = {"allow_public_ssh_keys": False} keys = ["key1"] user = "******" m_glob.return_value = [] # Return no matching keys to prevent removal # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) cloud = self.tmp_cloud(distro="ubuntu", metadata={"public-keys": keys}) cc_ssh.handle("name", cfg, cloud, LOG, None) options = ssh_util.DISABLE_USER_OPTS.replace("$USER", user) options = options.replace("$DISABLE_USER", "root") self.assertEqual( [ mock.call(set(), user), mock.call(set(), "root", options=options), ], m_setup_keys.call_args_list, )
def test_handle_no_cfg(self, m_path_exists, m_nug, m_glob, m_setup_keys): """Test handle with no config ignores generating existing keyfiles.""" cfg = {} keys = ["key1"] m_glob.return_value = [] # Return no matching keys to prevent removal # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ([], {}) cc_ssh.PUBLISH_HOST_KEYS = False cloud = self.tmp_cloud(distro="ubuntu", metadata={"public-keys": keys}) cc_ssh.handle("name", cfg, cloud, LOG, None) options = ssh_util.DISABLE_USER_OPTS.replace("$USER", "NONE") options = options.replace("$DISABLE_USER", "root") m_glob.assert_called_once_with("/etc/ssh/ssh_host_*key*") self.assertIn( [ mock.call("/etc/ssh/ssh_host_rsa_key"), mock.call("/etc/ssh/ssh_host_dsa_key"), mock.call("/etc/ssh/ssh_host_ecdsa_key"), mock.call("/etc/ssh/ssh_host_ed25519_key"), ], m_path_exists.call_args_list, ) self.assertEqual( [mock.call(set(keys), "root", options=options)], m_setup_keys.call_args_list, )
def test_handle_publish_hostkeys_config_enable(self, m_path_exists, m_nug, m_glob, m_setup_keys): """Test handle with various configs for ssh_publish_hostkeys.""" self._publish_hostkey_test_setup() cc_ssh.PUBLISH_HOST_KEYS = False keys = ["key1"] user = "******" # Return no matching keys for first glob, test keys for second. m_glob.side_effect = iter([ [], self.test_hostkey_files, ]) # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) cloud = self.tmp_cloud(distro='ubuntu', metadata={'public-keys': keys}) cloud.datasource.publish_host_keys = mock.Mock() cfg = {'ssh_publish_hostkeys': {'enabled': True}} expected_call = [ self.test_hostkeys[key_type] for key_type in ['ecdsa', 'ed25519', 'rsa'] ] cc_ssh.handle("name", cfg, cloud, LOG, None) self.assertEqual([mock.call(expected_call)], cloud.datasource.publish_host_keys.call_args_list)
def test_handle_ssh_keys_in_cfg(self, m_write_file, m_nug, m_setup_keys): """Test handle with ssh keys and certificate.""" # Populate a config dictionary to pass to handle() as well # as the expected file-writing calls. cfg = {"ssh_keys": {}} expected_calls = [] for key_type in cc_ssh.GENERATE_KEY_NAMES: private_name = "{}_private".format(key_type) public_name = "{}_public".format(key_type) cert_name = "{}_certificate".format(key_type) # Actual key contents don"t have to be realistic private_value = "{}_PRIVATE_KEY".format(key_type) public_value = "{}_PUBLIC_KEY".format(key_type) cert_value = "{}_CERT_KEY".format(key_type) cfg["ssh_keys"][private_name] = private_value cfg["ssh_keys"][public_name] = public_value cfg["ssh_keys"][cert_name] = cert_value expected_calls.extend( [ mock.call( "/etc/ssh/ssh_host_{}_key".format(key_type), private_value, 384, ), mock.call( "/etc/ssh/ssh_host_{}_key.pub".format(key_type), public_value, 384, ), mock.call( "/etc/ssh/ssh_host_{}_key-cert.pub".format(key_type), cert_value, 384, ), mock.call( "/etc/ssh/sshd_config", "HostCertificate /etc/ssh/ssh_host_{}_key-cert.pub" "\n".format(key_type), preserve_mode=True, ), ] ) # Run the handler. m_nug.return_value = ([], {}) with mock.patch( MODPATH + "ssh_util.parse_ssh_config", return_value=[] ): cc_ssh.handle( "name", cfg, self.tmp_cloud(distro="ubuntu"), LOG, None ) # Check that all expected output has been done. for call_ in expected_calls: self.assertIn(call_, m_write_file.call_args_list)
def test_handle_cfg_without_disable_root(self, m_path_exists, m_nug, m_glob, m_setup_keys): """Test handle with disable_root == False.""" # When disable_root == False, the ssh redirect for root is skipped cfg = {"disable_root": False} keys = ["key1"] user = "******" m_glob.return_value = [] # Return no matching keys to prevent removal # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) cloud = self.tmp_cloud(distro='ubuntu', metadata={'public-keys': keys}) cloud.get_public_ssh_keys = mock.Mock(return_value=keys) cc_ssh.handle("name", cfg, cloud, LOG, None) self.assertEqual([ mock.call(set(keys), user), mock.call(set(keys), "root", options="") ], m_setup_keys.call_args_list)
def test_handle_no_cfg_and_default_root(self, m_path_exists, m_nug, m_glob, m_setup_keys): """Test handle with no config and a default distro user.""" cfg = {} keys = ["key1"] user = "******" m_glob.return_value = [] # Return no matching keys to prevent removal # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) cloud = self.tmp_cloud(distro='ubuntu', metadata={'public-keys': keys}) cc_ssh.handle("name", cfg, cloud, LOG, None) options = ssh_util.DISABLE_USER_OPTS.replace("$USER", user) options = options.replace("$DISABLE_USER", "root") self.assertEqual([ mock.call(set(keys), user), mock.call(set(keys), "root", options=options) ], m_setup_keys.call_args_list)
def test_handle_publish_hostkeys( self, m_path_exists, m_nug, m_glob, m_setup_keys, publish_hostkey_test_setup, cfg, expected_key_types, ): """Test handle with various configs for ssh_publish_hostkeys.""" test_hostkeys, test_hostkey_files = publish_hostkey_test_setup cc_ssh.PUBLISH_HOST_KEYS = True keys = ["key1"] user = "******" # Return no matching keys for first glob, test keys for second. m_glob.side_effect = iter([ [], test_hostkey_files, ]) # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) cloud = get_cloud(distro="ubuntu", metadata={"public-keys": keys}) cloud.datasource.publish_host_keys = mock.Mock() expected_calls = [] if expected_key_types is not None: expected_calls = [ mock.call([ test_hostkeys[key_type] for key_type in expected_key_types ]) ] cc_ssh.handle("name", cfg, cloud, LOG, None) assert (expected_calls == cloud.datasource.publish_host_keys.call_args_list)