Merge "Add command to generate pubkey's signature"
This commit is contained in:
@@ -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,20 +380,30 @@ 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',
|
||||
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(
|
||||
@@ -373,28 +414,23 @@ def parse_cli_args(args=None):
|
||||
'http://api.refstack.net if it is not set '
|
||||
'(--url http://localhost:8000).')
|
||||
|
||||
shared_args.add_argument('-k', '--insecure',
|
||||
network_args.add_argument('-k', '--insecure',
|
||||
action='store_false',
|
||||
dest='insecure',
|
||||
required=False,
|
||||
help='Assume Yes to all prompt queries')
|
||||
help='Skip SSL checks while interacting '
|
||||
'with Refstack API')
|
||||
|
||||
shared_args.add_argument('-i', '--sign',
|
||||
network_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')
|
||||
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)
|
||||
|
||||
@@ -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'))
|
||||
|
||||
Reference in New Issue
Block a user