From 8280575b0b6772f1895e4df80cc74711ce12f038 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 15 Jun 2015 16:10:45 +0200 Subject: [PATCH] Support ssh-keygen of OpenSSH 6.8 OpenSSH 6.8 changed the default hash method to SHA256. OpenSSH 6.7 and older don't support the -E command line option to specify the hash method. First try without -E since most Linux distribution for OpenStack Kilo still use OpenSSH 6.7. If OpenSSH 6.8 and newer is detected (hash method specified in the output), call again ssh-keygen with -E md5 to hash the fingerprint using MD5. This change fixes the two following tests on Fedora 22: * nova.tests.unit.api.ec2.test_cloud.CloudTestCase.test_import_key_pair * nova.tests.unit.compute.test_keypairs.ImportKeypairTestCase.test_success_ssh Add two unit tests mocking OpenSSH 6.7 and 6.8 outputs. Closes-bug: #1464298 Change-Id: I867684c36377e5c1e5ca5d33e3fc2f1795f44e06 --- nova/crypto.py | 13 +++++++++- nova/tests/unit/test_crypto.py | 44 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/nova/crypto.py b/nova/crypto.py index 7194392e6552..83aad6e4c26b 100644 --- a/nova/crypto.py +++ b/nova/crypto.py @@ -127,8 +127,19 @@ def ensure_ca_filesystem(): def _generate_fingerprint(public_key_file): - (out, err) = utils.execute('ssh-keygen', '-q', '-l', '-f', public_key_file) + # NOTE(haypo): OpenSSH 6.7 and older doesn't support the -E option to + # specify the hash method. First try without -E since most Linux + # distribution for OpenStack Kilo still use OpenSSH 6.7. + args = ('ssh-keygen', '-q', '-l', '-f', public_key_file) + (out, err) = utils.execute(*args) fingerprint = out.split(' ')[1] + if fingerprint.startswith('SHA256:'): + # OpenSSH 6.8 and newer uses SHA256 by default: hash the fingerprint + # using MD5 for backward compatibility + (out, err) = utils.execute(*(args + ('-E', 'md5'))) + fingerprint = out.split(' ')[1] + # strip the hash method ("MD5:" prefix) + fingerprint = fingerprint[4:] return fingerprint diff --git a/nova/tests/unit/test_crypto.py b/nova/tests/unit/test_crypto.py index 2d4eac4d9a84..87b9fea38088 100644 --- a/nova/tests/unit/test_crypto.py +++ b/nova/tests/unit/test_crypto.py @@ -264,3 +264,47 @@ class ConversionTests(test.TestCase): def test_convert_failure(self): self.assertRaises(exception.EncryptionFailure, crypto.convert_from_sshrsa_to_pkcs8, '') + + +class FingerprintTests(test.TestCase): + @mock.patch.object(utils, 'execute') + def test_openssh_67(self, mock_execute): + # test ssh-keygen of OpenSSH 6.7 and older + mock_execute.side_effect = [ + (('2048 67:6e:f8:21:5c:b4:d5:95:74:54:e4:9e:f8:63:7d:60 ' + 'test (RSA)', + '')), + ] + + fingerprint = crypto.generate_fingerprint('public key') + self.assertEqual('67:6e:f8:21:5c:b4:d5:95:74:54:e4:9e:f8:63:7d:60', + fingerprint) + + self.assertEqual(1, len(mock_execute.call_args_list), + mock_execute.call_args) + self.assertEqual([('ssh-keygen', '-q', '-l', '-f', mock.ANY)], + mock_execute.call_args_list[0]) + + @mock.patch.object(utils, 'execute') + def test_openssh_68(self, mock_execute): + # test ssh-keygen of OpenSSH 6.8 and newer + mock_execute.side_effect = [ + (('2048 SHA256:p3Wk0H3odRa1ugL+7YNP5PC9R/OkUKlY6/6/Md2+yho ' + 'test (RSA)\n', + '')), + (('2048 MD5:b7:7d:ca:d5:31:9c:27:c6:b4:60:b2:d0:fa:0d:b4:9d ' + 'test (RSA)\n', + '')), + ] + + fingerprint = crypto.generate_fingerprint('public key') + self.assertEqual('b7:7d:ca:d5:31:9c:27:c6:b4:60:b2:d0:fa:0d:b4:9d', + fingerprint) + + self.assertEqual(2, len(mock_execute.call_args_list), + mock_execute.call_args) + self.assertEqual([('ssh-keygen', '-q', '-l', '-f', mock.ANY)], + mock_execute.call_args_list[0]) + self.assertEqual([('ssh-keygen', '-q', '-l', '-f', mock.ANY, + '-E', 'md5')], + mock_execute.call_args_list[1])