Replace ssh exec calls with paramiko lib
Nova already has a dependency on paramiko and therefore should take advantage of it for generating key pairs. This will reduce the code complexity and remove calls to exec. Change-Id: Ibb01f5227ded9b79816c064a06a1f6724f765e78
This commit is contained in:
parent
45c4fcb095
commit
3f3f9bf22e
|
@ -23,9 +23,11 @@ Includes root and intermediate CAs, SSH key_pairs and x509 certificates.
|
|||
from __future__ import absolute_import
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
import StringIO
|
||||
import struct
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
|
@ -33,6 +35,7 @@ from oslo_config import cfg
|
|||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import timeutils
|
||||
import paramiko
|
||||
from pyasn1.codec.der import encoder as der_encoder
|
||||
from pyasn1.type import univ
|
||||
|
||||
|
@ -126,22 +129,26 @@ def ensure_ca_filesystem():
|
|||
os.chdir(start)
|
||||
|
||||
|
||||
def _generate_fingerprint(public_key_file):
|
||||
(out, err) = utils.execute('ssh-keygen', '-q', '-l', '-f', public_key_file)
|
||||
fingerprint = out.split(' ')[1]
|
||||
return fingerprint
|
||||
|
||||
|
||||
def generate_fingerprint(public_key):
|
||||
with utils.tempdir() as tmpdir:
|
||||
try:
|
||||
pubfile = os.path.join(tmpdir, 'temp.pub')
|
||||
with open(pubfile, 'w') as f:
|
||||
f.write(public_key)
|
||||
return _generate_fingerprint(pubfile)
|
||||
except processutils.ProcessExecutionError:
|
||||
try:
|
||||
parts = public_key.split(' ')
|
||||
ssh_alg = parts[0]
|
||||
pub_data = parts[1].decode('base64')
|
||||
if ssh_alg == 'ssh-rsa':
|
||||
pkey = paramiko.RSAKey(data=pub_data)
|
||||
elif ssh_alg == 'ssh-dss':
|
||||
pkey = paramiko.DSSKey(data=pub_data)
|
||||
elif ssh_alg == 'ecdsa-sha2-nistp256':
|
||||
pkey = paramiko.ECDSAKey(data=pub_data, validate_point=False)
|
||||
else:
|
||||
raise exception.InvalidKeypair(
|
||||
reason=_('failed to generate fingerprint'))
|
||||
reason=_('Unknown ssh key type %s') % ssh_alg)
|
||||
raw_fp = binascii.hexlify(pkey.get_fingerprint())
|
||||
return ':'.join(a + b for a, b in zip(raw_fp[::2], raw_fp[1::2]))
|
||||
except (IndexError, UnicodeDecodeError, binascii.Error,
|
||||
paramiko.ssh_exception.SSHException):
|
||||
raise exception.InvalidKeypair(
|
||||
reason=_('failed to generate fingerprint'))
|
||||
|
||||
|
||||
def generate_x509_fingerprint(pem_key):
|
||||
|
@ -157,25 +164,13 @@ def generate_x509_fingerprint(pem_key):
|
|||
'Error message: %s') % ex)
|
||||
|
||||
|
||||
def generate_key_pair(bits=None):
|
||||
with utils.tempdir() as tmpdir:
|
||||
keyfile = os.path.join(tmpdir, 'temp')
|
||||
args = ['ssh-keygen', '-q', '-N', '', '-t', 'rsa',
|
||||
'-f', keyfile, '-C', 'Generated-by-Nova']
|
||||
if bits is not None:
|
||||
args.extend(['-b', bits])
|
||||
utils.execute(*args)
|
||||
fingerprint = _generate_fingerprint('%s.pub' % (keyfile))
|
||||
if not os.path.exists(keyfile):
|
||||
raise exception.FileNotFound(keyfile)
|
||||
with open(keyfile) as f:
|
||||
private_key = f.read()
|
||||
public_key_path = keyfile + '.pub'
|
||||
if not os.path.exists(public_key_path):
|
||||
raise exception.FileNotFound(public_key_path)
|
||||
with open(public_key_path) as f:
|
||||
public_key = f.read()
|
||||
|
||||
def generate_key_pair(bits=2048):
|
||||
key = paramiko.RSAKey.generate(bits)
|
||||
keyout = StringIO.StringIO()
|
||||
key.write_private_key(keyout)
|
||||
private_key = keyout.getvalue()
|
||||
public_key = '%s %s Generated-by-Nova' % (key.get_name(), key.get_base64())
|
||||
fingerprint = generate_fingerprint(public_key)
|
||||
return (private_key, public_key, fingerprint)
|
||||
|
||||
|
||||
|
|
|
@ -17,10 +17,12 @@ Tests for Crypto module.
|
|||
"""
|
||||
|
||||
import os
|
||||
import StringIO
|
||||
|
||||
import mock
|
||||
from mox3 import mox
|
||||
from oslo_concurrency import processutils
|
||||
import paramiko
|
||||
|
||||
from nova import crypto
|
||||
from nova import db
|
||||
|
@ -264,3 +266,103 @@ class ConversionTests(test.TestCase):
|
|||
def test_convert_failure(self):
|
||||
self.assertRaises(exception.EncryptionFailure,
|
||||
crypto.convert_from_sshrsa_to_pkcs8, '')
|
||||
|
||||
|
||||
class KeyPairTest(test.TestCase):
|
||||
rsa_prv = (
|
||||
"-----BEGIN RSA PRIVATE KEY-----\n"
|
||||
"MIIEowIBAAKCAQEA5G44D6lEgMj6cRwCPydsMl1VRN2B9DVyV5lmwssGeJClywZM\n"
|
||||
"WcKlSZBaWPbwbt20/r74eMGZPlqtEi9Ro+EHj4/n5+3A2Mh11h0PGSt53PSPfWwo\n"
|
||||
"ZhEg9hQ1w1ZxfBMCx7eG2YdGFQocMgR0zQasJGjjt8hruCnWRB3pNH9DhEwKhgET\n"
|
||||
"H0/CFzxSh0eZWs/O4GSf4upwmRG/1Yu90vnVZq3AanwvvW5UBk6g4uWb6FTES867\n"
|
||||
"kAy4b5EcH6WR3lLE09omuG/NqtH+qkgIdQconDkmkuK3xf5go6GSwEod0erM1G1v\n"
|
||||
"e+C4w/MD98KZ4Zlon9hy7oE2rcqHXf58gZtOTQIDAQABAoIBAQCnkeM2Oemyv7xY\n"
|
||||
"dT+ArJ7GY4lFt2i5iOuUL0ge5Wid0R6OTNR9lDhEOszMLno6GhHIPrdvfjW4dDQ5\n"
|
||||
"/tRY757oRZzNmq+5V3R52V9WC3qeCBmq3EjWdwJDAphd72/YoOmNMKiPsphKntwI\n"
|
||||
"JRS5wodNPlSuYSwEMUypM3f7ttAEn5CASgYgribBDapm7EqkVa2AqSvpFzNvN3/e\n"
|
||||
"Sc36/XlxJin7AkKVOnRksuVOOj504VUQfXgVWZkfTeZqAROgA1FSnjUAffcubJmq\n"
|
||||
"pDL/JSgOqN4S+sJkkTrb19MuM9M/IdXteloynF+GUKZx6FdVQQc8xCiXgeupeeSD\n"
|
||||
"fNMAP7DRAoGBAP0JRFm3fCAavBREKVOyZm20DpeR6zMrVP7ht0SykkT/bw/kiRG+\n"
|
||||
"FH1tNioj9uyixt5SiKhH3ZVAunjsKvrwET8i3uz1M2Gk+ovWdLXurBogYNNWafjQ\n"
|
||||
"hRhFHpyExoZYRsn58bvYvjFXTO6JxuNS2b59DGBRkQ5mpsOhxarfbZnXAoGBAOcb\n"
|
||||
"K+qoPDeDicnQZ8+ygYYHxY3fy1nvm1F19jBiWd26bAUOHeZNPPKGvTSlrGWJgEyA\n"
|
||||
"FjZIlHJOY2s0dhukiytOiXzdA5iqK1NvlF+QTUI4tCeNMVejWC+n6sKR9ADZkX8D\n"
|
||||
"NOHaLkDzc/ukus59aKyjxP53I6SV6y6m5NeyvDx7AoGAaUji1MXA8wbMvU4DOB0h\n"
|
||||
"+4GRFMYVbEwaaJd4jzASJn12M9GuquBBXFMF15DxXFL6lmUXEZYdf83YCRqTY6hi\n"
|
||||
"NLgIs+XuxDFGQssv8sdletWAFE9/dpUk3A1eiFfC1wGCKuZCDBxKPvOJQjO3uryt\n"
|
||||
"d1JGxQkLZ0eVGg+E1O10iC8CgYB4w2QRfNPqllu8D6EPkVHJfeonltgmKOTajm+V\n"
|
||||
"HO+kw7OKeLP7EkVU3j+kcSZC8LUQRKZWu1qG2Jtu+7zz+OmYObPygXNNpS56rQW1\n"
|
||||
"Yixc/FB3knpEN2DvlilAfxAoGYjD/CL4GhCtdAoZZx0Opc262OEpr4v6hzSb7i4K\n"
|
||||
"4KUoXQKBgHfbiaSilxx9guUqvSaexpHmtiUwx05a05fD6tu8Cofl6AM9wGpw3xOT\n"
|
||||
"tfo4ehvS13tTz2RDE2xKuetMmkya7UgifcxUmBzqkOlgr0oOi2rp+eDKXnzUUqsH\n"
|
||||
"V7E96Dj36K8q2+gZIXcNqjN7PzfkF8pA0G+E1veTi8j5dnvIsy1x\n"
|
||||
"-----END RSA PRIVATE KEY-----\n"
|
||||
)
|
||||
|
||||
rsa_pub = (
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDkbjgPqUSAyPpxHAI/J2wyXVVE"
|
||||
"3YH0NXJXmWbCywZ4kKXLBkxZwqVJkFpY9vBu3bT+vvh4wZk+Wq0SL1Gj4QePj+fn"
|
||||
"7cDYyHXWHQ8ZK3nc9I99bChmESD2FDXDVnF8EwLHt4bZh0YVChwyBHTNBqwkaOO3"
|
||||
"yGu4KdZEHek0f0OETAqGARMfT8IXPFKHR5laz87gZJ/i6nCZEb/Vi73S+dVmrcBq"
|
||||
"fC+9blQGTqDi5ZvoVMRLzruQDLhvkRwfpZHeUsTT2ia4b82q0f6qSAh1ByicOSaS"
|
||||
"4rfF/mCjoZLASh3R6szUbW974LjD8wP3wpnhmWif2HLugTatyodd/nyBm05N Gen"
|
||||
"erated-by-Nova"
|
||||
)
|
||||
|
||||
rsa_fp = "e7:66:a1:2c:4f:90:6e:11:19:da:ac:c2:69:e1:ad:89"
|
||||
|
||||
dss_pub = (
|
||||
"ssh-dss AAAAB3NzaC1kc3MAAACBAKWFW2++pDxJWObkADbSXw8KfZ4VupkRKEXF"
|
||||
"SPN2kV0v+FgdnBEcrEJPExaOTMhmxIuc82ktTv76wHSEpbbsLuI7IDbB6KJJwHs2"
|
||||
"y356yB28Q9rin7X0VMYKkPxvAcbIUSrEbQtyPMihlOaaQ2dGSsEQGQSpjm3f3RU6"
|
||||
"OWux0w/NAAAAFQCgzWF2zxQmi/Obd11z9Im6gY02gwAAAIAHCDLjipVwMLXIqNKO"
|
||||
"MktiPex+ewRQxBi80dzZ3mJzARqzLPYI9hJFUU0LiMtLuypV/djpUWN0cQpmgTQf"
|
||||
"TfuZx9ipC6Mtiz66NQqjkQuoihzdk+9KlOTo03UsX5uBGwuZ09Dnf1VTF8ZsW5Hg"
|
||||
"HyOk6qD71QBajkcFJAKOT3rFfgAAAIAy8trIzqEps9/n37Nli1TvNPLbFQAXl1LN"
|
||||
"wUFmFDwBCGTLl8puVZv7VSu1FG8ko+mzqNebqcN4RMC26NxJqe+RRubn5KtmLoIa"
|
||||
"7tRe74hvQ1HTLLuGxugwa4CewNbwzzEDEs8U79WDhGKzDkJR4nLPVimj5WLAWV70"
|
||||
"RNnRX7zj5w== Generated-by-Nova"
|
||||
)
|
||||
|
||||
dss_fp = "b9:dc:ac:57:df:2a:2b:cf:65:a8:c3:4e:9d:4a:82:3c"
|
||||
|
||||
ecdsa_pub = (
|
||||
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy"
|
||||
"NTYAAABBBG1r4wzPTIjSo78POCq+u/czb8gYK0KvqlmCvcRPrnDWxgLw7y6BX51t"
|
||||
"uYREz7iLRCP7BwUt8R+ZWzFZDeOLIWU= Generated-by-Nova"
|
||||
)
|
||||
|
||||
ecdsa_fp = "16:6a:c9:ec:80:4d:17:3e:d5:3b:6f:c0:d7:15:04:40"
|
||||
|
||||
def test_generate_fingerprint(self):
|
||||
fingerprint = crypto.generate_fingerprint(self.rsa_pub)
|
||||
self.assertEqual(self.rsa_fp, fingerprint)
|
||||
|
||||
fingerprint = crypto.generate_fingerprint(self.dss_pub)
|
||||
self.assertEqual(self.dss_fp, fingerprint)
|
||||
|
||||
fingerprint = crypto.generate_fingerprint(self.ecdsa_pub)
|
||||
self.assertEqual(self.ecdsa_fp, fingerprint)
|
||||
|
||||
def test_generate_key_pair(self):
|
||||
(private_key, public_key, fingerprint) = crypto.generate_key_pair()
|
||||
raw_pub = public_key.split(' ')[1].decode('base64')
|
||||
pkey = paramiko.rsakey.RSAKey(None, raw_pub)
|
||||
self.assertEqual(2048, pkey.get_bits())
|
||||
|
||||
bits = 4096
|
||||
(private_key, public_key, fingerprint) = crypto.generate_key_pair(bits)
|
||||
raw_pub = public_key.split(' ')[1].decode('base64')
|
||||
pkey = paramiko.rsakey.RSAKey(None, raw_pub)
|
||||
self.assertEqual(bits, pkey.get_bits())
|
||||
|
||||
keyin = StringIO.StringIO()
|
||||
keyin.write(self.rsa_prv)
|
||||
keyin.seek(0)
|
||||
key = paramiko.RSAKey.from_private_key(keyin)
|
||||
|
||||
with mock.patch.object(paramiko.RSAKey, 'generate') as mock_generate:
|
||||
mock_generate.return_value = key
|
||||
(private_key, public_key, fingerprint) = crypto.generate_key_pair()
|
||||
self.assertEqual(self.rsa_pub, public_key)
|
||||
self.assertEqual(self.rsa_fp, fingerprint)
|
||||
|
|
Loading…
Reference in New Issue