Merge "Add command to generate pubkey's signature"

This commit is contained in:
Jenkins
2015-08-19 15:54:34 +00:00
committed by Gerrit Code Review
2 changed files with 107 additions and 44 deletions

View File

@@ -342,6 +342,37 @@ class RefstackClient:
except KeyboardInterrupt:
return
def _sign_pubkey(self):
"""Generate self signature for public key"""
try:
with open(self.args.priv_key_to_sign) as priv_key_file:
private_key = RSA.importKey(priv_key_file.read())
except (IOError, ValueError) as e:
self.logger.error('Error reading private key %s'
'' % self.args.priv_key_to_sign)
self.logger.exception(e)
return
pubkey_filename = '.'.join((self.args.priv_key_to_sign, 'pub'))
try:
with open(pubkey_filename) as pub_key_file:
pub_key = pub_key_file.read()
except IOError:
self.logger.error('Public key file %s not found. '
'Public key is generated from private one.'
'' % pubkey_filename)
pub_key = private_key.publickey().exportKey('OpenSSH')
data_hash = SHA256.new()
data_hash.update('signature'.encode('utf-8'))
signer = PKCS1_v1_5.new(private_key)
signature = binascii.b2a_hex(signer.sign(data_hash))
return pub_key, signature
def self_sign(self):
"""Generate signature for public key."""
pub_key, signature = self._sign_pubkey()
print('Public key:\n%s\n' % pub_key)
print('Self signature:\n%s\n' % signature)
def parse_cli_args(args=None):
@@ -349,52 +380,57 @@ def parse_cli_args(args=None):
'To see help on specific argument, do:\n'
'refstack-client <ARG> -h')
parser = argparse.ArgumentParser(description='Refstack-client arguments',
formatter_class=argparse.
ArgumentDefaultsHelpFormatter,
usage=usage_string)
parser = argparse.ArgumentParser(
description='Refstack-client arguments',
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
usage=usage_string
)
subparsers = parser.add_subparsers(help='Available subcommands.')
# Arguments that go with all subcommands.
shared_args = argparse.ArgumentParser(add_help=False)
shared_args.add_argument('-v', '--verbose',
action='count',
help='Show verbose output.')
shared_args.add_argument('--url',
action='store',
required=False,
default=os.environ.get(
'REFSTACK_URL', 'http://api.refstack.net'),
type=str,
help='Refstack API URL to upload results to. '
'Defaults to env[REFSTACK_URL] or '
'http://api.refstack.net if it is not set '
'(--url http://localhost:8000).')
shared_args.add_argument('-k', '--insecure',
action='store_false',
dest='insecure',
required=False,
help='Assume Yes to all prompt queries')
shared_args.add_argument('-i', '--sign',
type=str,
required=False,
dest='priv_key',
help='Private RSA key. '
'OpenSSH RSA keys format supported ('
'-i ~/.ssh/id-rsa)')
shared_args.add_argument('-y',
action='store_true',
dest='quiet',
required=False,
help='Assume Yes to all prompt queries')
# Arguments that go with network-related subcommands (test, list, etc.).
network_args = argparse.ArgumentParser(add_help=False)
network_args.add_argument('--url',
action='store',
required=False,
default=os.environ.get(
'REFSTACK_URL', 'http://api.refstack.net'),
type=str,
help='Refstack API URL to upload results to. '
'Defaults to env[REFSTACK_URL] or '
'http://api.refstack.net if it is not set '
'(--url http://localhost:8000).')
network_args.add_argument('-k', '--insecure',
action='store_false',
dest='insecure',
required=False,
help='Skip SSL checks while interacting '
'with Refstack API')
network_args.add_argument('-i', '--sign',
type=str,
required=False,
dest='priv_key',
help='Path to private RSA key. '
'OpenSSH RSA keys format supported')
# Upload command
parser_upload = subparsers.add_parser(
'upload', parents=[shared_args],
'upload', parents=[shared_args, network_args],
help='Upload an existing result file.'
)
@@ -406,7 +442,7 @@ def parse_cli_args(args=None):
# Test command
parser_test = subparsers.add_parser(
'test', parents=[shared_args],
'test', parents=[shared_args, network_args],
help='Run Tempest against a cloud.')
parser_test.add_argument('-c', '--conf-file',
@@ -457,7 +493,7 @@ def parse_cli_args(args=None):
# List command
parser_list = subparsers.add_parser(
'list', parents=[shared_args],
'list', parents=[shared_args, network_args],
help='List last results from Refstack')
parser_list.add_argument('--start-date',
required=False,
@@ -475,4 +511,16 @@ def parse_cli_args(args=None):
'(e.g. --end-date "2015-04-24 01:23:56").')
parser_list.set_defaults(func='list')
# Sign command
parser_sign = subparsers.add_parser(
'sign', parents=[shared_args],
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
help='Generate signature for public key.')
parser_sign.add_argument('priv_key_to_sign',
type=str,
help='Path to private RSA key. '
'OpenSSH RSA keys format supported')
parser_sign.set_defaults(func='self_sign')
return parser.parse_args(args=args)

View File

@@ -52,13 +52,12 @@ class TestRefstackClient(unittest.TestCase):
:param verbose: verbosity level
:return: argv
"""
argv = [command,
'--url', 'http://127.0.0.1', '-y']
if kwargs.get('priv_key', None):
argv.extend(('-i', kwargs.get('priv_key', None)))
argv = [command]
if kwargs.get('verbose', None):
argv.append(kwargs.get('verbose', None))
argv.extend(['--url', 'http://127.0.0.1', '-y'])
if kwargs.get('priv_key', None):
argv.extend(('-i', kwargs.get('priv_key', None)))
if command == 'test':
argv.extend(
('-c', kwargs.get('conf_file_name', self.conf_file_name)))
@@ -271,7 +270,9 @@ class TestRefstackClient(unittest.TestCase):
"""
Test the post_results method, ensuring a requests call is made.
"""
args = rc.parse_cli_args(self.mock_argv(priv_key='rsa_key'))
argv = self.mock_argv(command='upload', priv_key='rsa_key')
argv.append('fake.json')
args = rc.parse_cli_args(argv)
client = rc.RefstackClient(args)
client.logger.info = MagicMock()
content = {'duration_seconds': 0,
@@ -326,7 +327,7 @@ class TestRefstackClient(unittest.TestCase):
"""
argv = self.mock_argv(verbose='-vv',
test_cases='tempest.api.compute')
argv.insert(1, '--upload')
argv.insert(2, '--upload')
args = rc.parse_cli_args(argv)
client = rc.RefstackClient(args)
client.tempest_dir = self.test_path
@@ -354,13 +355,14 @@ class TestRefstackClient(unittest.TestCase):
"""
argv = self.mock_argv(verbose='-vv', priv_key='rsa_key',
test_cases='tempest.api.compute')
argv.insert(1, '--upload')
argv.insert(2, '--upload')
args = rc.parse_cli_args(argv)
client = rc.RefstackClient(args)
client.tempest_dir = self.test_path
mock_popen = self.patch(
'refstack_client.refstack_client.subprocess.Popen',
return_value=MagicMock(returncode=0))
return_value=MagicMock(returncode=0)
)
self.patch("os.path.isfile", return_value=True)
self.mock_keystone()
client.get_passed_tests = MagicMock(return_value=['test'])
@@ -433,8 +435,8 @@ class TestRefstackClient(unittest.TestCase):
"""
argv = self.mock_argv(verbose='-vv',
test_cases='tempest.api.compute')
argv.insert(1, '--result-file-tag')
argv.insert(2, 'my-test')
argv.insert(2, '--result-file-tag')
argv.insert(3, 'my-test')
args = rc.parse_cli_args(argv)
client = rc.RefstackClient(args)
client.tempest_dir = self.test_path
@@ -479,7 +481,8 @@ class TestRefstackClient(unittest.TestCase):
"""
upload_file_path = self.test_path + "/.testrepository/0.json"
args = rc.parse_cli_args(
self.mock_argv(command='upload') + [upload_file_path])
self.mock_argv(command='upload', priv_key='rsa_key')
+ [upload_file_path])
client = rc.RefstackClient(args)
client.post_results = MagicMock()
@@ -495,7 +498,7 @@ class TestRefstackClient(unittest.TestCase):
}
client.post_results.assert_called_with('http://127.0.0.1',
expected_json,
sign_with=None)
sign_with='rsa_key')
def test_upload_nonexisting_file(self):
"""
@@ -559,3 +562,15 @@ class TestRefstackClient(unittest.TestCase):
client.yield_results = MagicMock(return_value=mock_results)
client.list()
self.assertTrue(mock_stdout.write.called)
def test_sign_pubkey(self):
"""
Test that the test command will run the tempest script and call
post_results when the --upload argument is passed in.
"""
args = rc.parse_cli_args(['sign',
os.path.join(self.test_path, 'rsa_key')])
client = rc.RefstackClient(args)
pubkey, signature = client._sign_pubkey()
self.assertTrue(pubkey.startswith('ssh-rsa AAAA'))
self.assertTrue(signature.startswith('413cb954'))