Add command to generate pubkey's signature
Uploading public key in to Refstack requires pub key self-signature This signature can be generated with refstac-client using "sign" command. For example: ./refstack-client sign ~/.ssh/id_rsa Will print public key ~/.ssh/id_rsa.pub and self signature for it. Change-Id: I0ee3e89c5e8bcae85eae63500a5fd98cea12c273
This commit is contained in:
@@ -342,6 +342,37 @@ class RefstackClient:
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
return
|
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):
|
def parse_cli_args(args=None):
|
||||||
|
|
||||||
@@ -349,52 +380,57 @@ def parse_cli_args(args=None):
|
|||||||
'To see help on specific argument, do:\n'
|
'To see help on specific argument, do:\n'
|
||||||
'refstack-client <ARG> -h')
|
'refstack-client <ARG> -h')
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Refstack-client arguments',
|
parser = argparse.ArgumentParser(
|
||||||
formatter_class=argparse.
|
description='Refstack-client arguments',
|
||||||
ArgumentDefaultsHelpFormatter,
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||||
usage=usage_string)
|
usage=usage_string
|
||||||
|
)
|
||||||
|
|
||||||
subparsers = parser.add_subparsers(help='Available subcommands.')
|
subparsers = parser.add_subparsers(help='Available subcommands.')
|
||||||
|
|
||||||
# Arguments that go with all subcommands.
|
# Arguments that go with all subcommands.
|
||||||
shared_args = argparse.ArgumentParser(add_help=False)
|
shared_args = argparse.ArgumentParser(add_help=False)
|
||||||
|
|
||||||
shared_args.add_argument('-v', '--verbose',
|
shared_args.add_argument('-v', '--verbose',
|
||||||
action='count',
|
action='count',
|
||||||
help='Show verbose output.')
|
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',
|
shared_args.add_argument('-y',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
dest='quiet',
|
dest='quiet',
|
||||||
required=False,
|
required=False,
|
||||||
help='Assume Yes to all prompt queries')
|
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
|
# Upload command
|
||||||
parser_upload = subparsers.add_parser(
|
parser_upload = subparsers.add_parser(
|
||||||
'upload', parents=[shared_args],
|
'upload', parents=[shared_args, network_args],
|
||||||
help='Upload an existing result file.'
|
help='Upload an existing result file.'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -406,7 +442,7 @@ def parse_cli_args(args=None):
|
|||||||
|
|
||||||
# Test command
|
# Test command
|
||||||
parser_test = subparsers.add_parser(
|
parser_test = subparsers.add_parser(
|
||||||
'test', parents=[shared_args],
|
'test', parents=[shared_args, network_args],
|
||||||
help='Run Tempest against a cloud.')
|
help='Run Tempest against a cloud.')
|
||||||
|
|
||||||
parser_test.add_argument('-c', '--conf-file',
|
parser_test.add_argument('-c', '--conf-file',
|
||||||
@@ -457,7 +493,7 @@ def parse_cli_args(args=None):
|
|||||||
|
|
||||||
# List command
|
# List command
|
||||||
parser_list = subparsers.add_parser(
|
parser_list = subparsers.add_parser(
|
||||||
'list', parents=[shared_args],
|
'list', parents=[shared_args, network_args],
|
||||||
help='List last results from Refstack')
|
help='List last results from Refstack')
|
||||||
parser_list.add_argument('--start-date',
|
parser_list.add_argument('--start-date',
|
||||||
required=False,
|
required=False,
|
||||||
@@ -475,4 +511,16 @@ def parse_cli_args(args=None):
|
|||||||
'(e.g. --end-date "2015-04-24 01:23:56").')
|
'(e.g. --end-date "2015-04-24 01:23:56").')
|
||||||
parser_list.set_defaults(func='list')
|
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)
|
return parser.parse_args(args=args)
|
||||||
|
|||||||
@@ -52,13 +52,12 @@ class TestRefstackClient(unittest.TestCase):
|
|||||||
:param verbose: verbosity level
|
:param verbose: verbosity level
|
||||||
:return: argv
|
:return: argv
|
||||||
"""
|
"""
|
||||||
argv = [command,
|
argv = [command]
|
||||||
'--url', 'http://127.0.0.1', '-y']
|
|
||||||
if kwargs.get('priv_key', None):
|
|
||||||
argv.extend(('-i', kwargs.get('priv_key', None)))
|
|
||||||
if kwargs.get('verbose', None):
|
if kwargs.get('verbose', None):
|
||||||
argv.append(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':
|
if command == 'test':
|
||||||
argv.extend(
|
argv.extend(
|
||||||
('-c', kwargs.get('conf_file_name', self.conf_file_name)))
|
('-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.
|
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 = rc.RefstackClient(args)
|
||||||
client.logger.info = MagicMock()
|
client.logger.info = MagicMock()
|
||||||
content = {'duration_seconds': 0,
|
content = {'duration_seconds': 0,
|
||||||
@@ -326,7 +327,7 @@ class TestRefstackClient(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
argv = self.mock_argv(verbose='-vv',
|
argv = self.mock_argv(verbose='-vv',
|
||||||
test_cases='tempest.api.compute')
|
test_cases='tempest.api.compute')
|
||||||
argv.insert(1, '--upload')
|
argv.insert(2, '--upload')
|
||||||
args = rc.parse_cli_args(argv)
|
args = rc.parse_cli_args(argv)
|
||||||
client = rc.RefstackClient(args)
|
client = rc.RefstackClient(args)
|
||||||
client.tempest_dir = self.test_path
|
client.tempest_dir = self.test_path
|
||||||
@@ -354,13 +355,14 @@ class TestRefstackClient(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
argv = self.mock_argv(verbose='-vv', priv_key='rsa_key',
|
argv = self.mock_argv(verbose='-vv', priv_key='rsa_key',
|
||||||
test_cases='tempest.api.compute')
|
test_cases='tempest.api.compute')
|
||||||
argv.insert(1, '--upload')
|
argv.insert(2, '--upload')
|
||||||
args = rc.parse_cli_args(argv)
|
args = rc.parse_cli_args(argv)
|
||||||
client = rc.RefstackClient(args)
|
client = rc.RefstackClient(args)
|
||||||
client.tempest_dir = self.test_path
|
client.tempest_dir = self.test_path
|
||||||
mock_popen = self.patch(
|
mock_popen = self.patch(
|
||||||
'refstack_client.refstack_client.subprocess.Popen',
|
'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.patch("os.path.isfile", return_value=True)
|
||||||
self.mock_keystone()
|
self.mock_keystone()
|
||||||
client.get_passed_tests = MagicMock(return_value=['test'])
|
client.get_passed_tests = MagicMock(return_value=['test'])
|
||||||
@@ -433,8 +435,8 @@ class TestRefstackClient(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
argv = self.mock_argv(verbose='-vv',
|
argv = self.mock_argv(verbose='-vv',
|
||||||
test_cases='tempest.api.compute')
|
test_cases='tempest.api.compute')
|
||||||
argv.insert(1, '--result-file-tag')
|
argv.insert(2, '--result-file-tag')
|
||||||
argv.insert(2, 'my-test')
|
argv.insert(3, 'my-test')
|
||||||
args = rc.parse_cli_args(argv)
|
args = rc.parse_cli_args(argv)
|
||||||
client = rc.RefstackClient(args)
|
client = rc.RefstackClient(args)
|
||||||
client.tempest_dir = self.test_path
|
client.tempest_dir = self.test_path
|
||||||
@@ -479,7 +481,8 @@ class TestRefstackClient(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
upload_file_path = self.test_path + "/.testrepository/0.json"
|
upload_file_path = self.test_path + "/.testrepository/0.json"
|
||||||
args = rc.parse_cli_args(
|
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 = rc.RefstackClient(args)
|
||||||
|
|
||||||
client.post_results = MagicMock()
|
client.post_results = MagicMock()
|
||||||
@@ -495,7 +498,7 @@ class TestRefstackClient(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
client.post_results.assert_called_with('http://127.0.0.1',
|
client.post_results.assert_called_with('http://127.0.0.1',
|
||||||
expected_json,
|
expected_json,
|
||||||
sign_with=None)
|
sign_with='rsa_key')
|
||||||
|
|
||||||
def test_upload_nonexisting_file(self):
|
def test_upload_nonexisting_file(self):
|
||||||
"""
|
"""
|
||||||
@@ -559,3 +562,15 @@ class TestRefstackClient(unittest.TestCase):
|
|||||||
client.yield_results = MagicMock(return_value=mock_results)
|
client.yield_results = MagicMock(return_value=mock_results)
|
||||||
client.list()
|
client.list()
|
||||||
self.assertTrue(mock_stdout.write.called)
|
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