add ability to cli/api to add/clear ssh keys in password file

- add new password setkey cli command
- add new api password_set_sshkey
- add ability to param checker to not display bad param
- add new change_password util for kolla_actions to call
- update kolla_actions to handle ssh keys
- add utest for setting/clearing ssh keys

Change-Id: I1fedb85d21cd04c222f7250bdda66ad42a9ddca3
Jira-Issue: OPENSTACK-1071
This commit is contained in:
Steve Noyes 2016-09-20 15:26:49 +02:00
parent 8858b0fd16
commit 3f029a7bd4
7 changed files with 177 additions and 16 deletions

View File

@ -16,6 +16,7 @@ import kollacli.i18n as u
from kollacli.common.passwords import clear_password
from kollacli.common.passwords import get_password_names
from kollacli.common.passwords import set_password
from kollacli.common.passwords import set_password_sshkey
from kollacli.common.utils import check_arg
@ -31,8 +32,25 @@ class PasswordApi(object):
:type value: string
"""
check_arg(name, u._('Password name'), str)
check_arg(value, u._('Password value'), str, display_param=False)
set_password(name, value)
def password_set_sshkey(self, name, private_key, public_key):
# type: (str, str, str) -> None
"""Set password to an ssh key
:param name: name of the password
:type name: string
:param private_key: ssh private key
:type value: string
:param public_key: ssh public key
:type value: string
"""
check_arg(name, u._('Password name'), str)
check_arg(private_key, u._('Private key'), str, display_param=False)
check_arg(public_key, u._('Public key'), str, display_param=False)
set_password_sshkey(name, private_key, public_key)
def password_clear(self, name):
# type: (str) -> None
"""Clear password

View File

@ -13,6 +13,7 @@
# under the License.
import argparse
import getpass
import os
import traceback
import kollacli.i18n as u
@ -54,6 +55,45 @@ class PasswordSet(Command):
raise Exception(traceback.format_exc())
class PasswordSetKey(Command):
"Password Set SSH Key"
def get_parser(self, prog_name):
parser = super(PasswordSetKey, self).get_parser(prog_name)
parser.add_argument('passwordname', metavar='<passwordname>',
help=u._('Password name'))
parser.add_argument('privatekeypath', metavar='<privatekeypath>',
help=u._('Path to private key file'))
parser.add_argument('publickeypath', metavar='<publickeypath>',
help=u._('Path to public key file'))
return parser
def take_action(self, parsed_args):
try:
password_name = parsed_args.passwordname.strip()
private_keypath = parsed_args.privatekeypath.strip()
private_keypath = os.path.abspath(private_keypath)
public_keypath = parsed_args.publickeypath.strip()
public_keypath = os.path.abspath(public_keypath)
if not os.path.isfile(private_keypath):
raise(CommandError(u._('Private key file not found: {path}')
.format(path=private_keypath)))
if not os.path.isfile(public_keypath):
raise(CommandError(u._('Public key file not found: {path}')
.format(path=public_keypath)))
with open(private_keypath, 'r') as f:
private_key = f.read()
with open(public_keypath, 'r') as f:
public_key = f.read()
CLIENT.password_set_sshkey(password_name, private_key.strip(),
public_key.strip())
except Exception:
raise Exception(traceback.format_exc())
class PasswordClear(Command):
"Password Clear"

View File

@ -35,6 +35,16 @@ def set_password(pwd_key, pwd_value):
.format(error=err_msg, message=output))
def set_password_sshkey(pwd_key, private_key, public_key):
cmd = '%s -k %s -r "%s" -u "%s"' % (_get_cmd_prefix(), pwd_key,
private_key, public_key)
err_msg, output = utils.run_cmd(cmd, print_output=False)
if err_msg:
raise FailedOperation(
u._('Password ssh key set failed. {error} {message}')
.format(error=err_msg, message=output))
def clear_password(pwd_key):
"""clear a password

View File

@ -21,6 +21,7 @@ import six
import subprocess # nosec
import sys
import time
import yaml
import kollacli.i18n as u
@ -196,6 +197,42 @@ def run_cmd(cmd, print_output=True):
return err, output
def change_password(file_path, pname, pvalue=None, public_key=None,
private_key=None, clear=False):
"""change password in passwords.yml file
file_path: path to passwords file
pname: name of password
pvalue: value of password when not ssh key
public_key: public ssh key
private_key: private ssh key
clear: flag to remove password
If clear, and password exists, remove it from the password file.
If clear, and password doesn't exists, nothing is done.
If not clear, and key is not found, the new password will be added.
If not clear, and key is found, edit password in place.
The passwords file contains both key-value pairs and key-dictionary
pairs.
"""
read_data = sync_read_file(file_path)
file_pwds = yaml.safe_load(read_data)
if clear:
# clear
if pname in file_pwds:
del file_pwds[pname]
else:
# edit
if pvalue:
file_pwds[pname] = pvalue
elif private_key:
file_pwds[pname] = {'private_key': private_key,
'public_key': public_key}
write_data = yaml.safe_dump(file_pwds, default_flow_style=False)
sync_write_file(file_path, write_data)
def change_property(file_path, property_dict, clear=False):
"""change property with a file
@ -375,7 +412,8 @@ def convert_list_to_string(alist):
return '[' + ','.join(alist) + ']'
def check_arg(param, param_name, expected_type, none_ok=False, empty_ok=False):
def check_arg(param, param_name, expected_type, none_ok=False, empty_ok=False,
display_param=True):
if param is None:
if none_ok:
return
@ -395,9 +433,14 @@ def check_arg(param, param_name, expected_type, none_ok=False, empty_ok=False):
if not isinstance(param, expected_type):
# wrong type
raise InvalidArgument(u._('{name} ({param}) is not a {type}')
.format(name=param_name, param=param,
type=expected_type))
if display_param:
raise InvalidArgument(u._('{name} ({param}) is not a {type}')
.format(name=param_name, param=param,
type=expected_type))
else:
raise InvalidArgument(u._('{name} is not a {type}')
.format(name=param_name,
type=expected_type))
class Lock(object):

View File

@ -48,6 +48,7 @@ kolla.cli =
password_clear = kollacli.commands.password:PasswordClear
password_list = kollacli.commands.password:PasswordList
password_set = kollacli.commands.password:PasswordSet
password_setkey = kollacli.commands.password:PasswordSetKey
property_clear = kollacli.commands.property:PropertyClear
property_list = kollacli.commands.property:PropertyList
property_set = kollacli.commands.property:PropertySet

View File

@ -16,8 +16,40 @@ from common import KollaCliTest
import os
import unittest
from kollacli.api import client
from kollacli.common.utils import get_kolla_etc
CLIENT = client.ClientApi()
PUBLIC_KEY = (
'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCqusDp5jpkbng3sRue8gZV6/PCQp9'
'ogUd5/OZ3sh9VgdaigHoYUfXZElTZLlkL71tD9WZJr69PDwmG/nE4quba8rLcDY2wC0'
'qjq+r06ExhlRu4ivy7OxT29s8FSe8Uht9Pz8ahnXxddLF55yTbC81XrSXDBFc6Nnogz'
'+g6GgXVKtwTkm5g3K+qix5zVECu8zzawBR/s+v0dkDxKwSY8XOG6JZlMUndaDaikZZi'
'qp8KAOJpajM77aCfDkY3VZGFBCJiEGLVDhFrtXuBI9I0YzX4j9pZZWpSzkM/FwlPjDR'
'SW1C9MAAFLoEQTN4j1Z5hkDNXDsr49wJBi+jjQ0FPMMvfJktrznRuO2fUa9W2iilOrv'
'1PyrknssmW1iYXiWJ5Bq8A9sKE1r7Nbdjhcjskp77X57tNjtarRUcj3FqGjC8pv+k92'
'9Y+FvkXbjpBsHpdMFh8BlM+EnwnsjkiQpmjLv8bpeeQooLyQQmZn94zY73bbGrsjzXe'
'OhOTDnKAS14hxCnBlEbudHB4erp/5Nj+A8UVAT0KXPM+mkDrum/dsvV0wnvBicAVt/a'
'tmkwDKJqXDmj4elNe8/jTXSYHpTDo29xtcGpka9AtWarmnt8QkRuieD1xSXsEUQswjq'
'aQD2ikitKt/hEyCmT+7fy4yYKK35kukUj5qV85A8O/hOYf5vFjtRw==')
PRIVATE_KEY = (
'-----BEGIN RSA PRIVATE KEY-----\n'
'MIIJKAIBAAKCAgEAqrrA6eY6ZG54N7EbnvIGVevzwkKfaIFHefzmd7IfVYHWooB6\n'
'GFH12RJU2S5ZC+9bQ/VmSa+vTw8Jhv5xOKrm2vKy3A2NsAtKo6vq9OhMYZUbuIr8\n'
'uzsU9vbPBUnvFIbfT8/GoZ18XXSxeeck2wvNV60lwwRXOjZ6IPoOhoF1SrcE5JuY\n'
'Nyvqosec1RArvM82sAUf7Pr9HZA8SsEmPFzhuiWZTFJ3Wg2opGWYqqfCgDiaWozO\n'
'+2gnw5GN1WRhQQiYhBi1Q4Ra7V7gSPSNGM1+I/aWWVqUs5DPxcJT4w0UltQvTAAB\n'
'S6BEEzeI9WeYZAzVw7K+PcCQYvo40NBTzDL3yZLa850bjtn1GvVtoopTq79T8q5J\n'
'7LJltYmF4lieQavAPbChNa+zW3Y4XI7JKe+1+e7TY7Wq0VHI9xahowvKb/pPdvWP\n'
'hb5F246QbB6XTBYfAZTPhJ8J7I5IkKZoy7/G6XnkKKC8kEJmZ/eM2O922xq7I813\n'
'joTkw5ygEteIcQpwZRG7nRweHq6f+TY/gPFFQE9ClzzPppA67pv3bL1dMJ7wYnAF\n'
'69VedCYMSoYIHpcN80w9it/6Cfm8niAy3v9e0icSVEsvkzcV6eFjLggY1DQ9WBPN\n'
'MR4LKGNDuxEWeZAQi+A6Ejclx1KKBhL/E4SNj3ev4/5glaMjzSIUpA4415o=\n'
'-----END RSA PRIVATE KEY-----'
)
class TestFunctional(KollaCliTest):
@ -66,6 +98,15 @@ class TestFunctional(KollaCliTest):
'(%s/%s) not in output: %s' %
(key, value, msg))
# test setting/clearing an ssh key
key = 'TeStKeY'
CLIENT.password_set_sshkey(key, PRIVATE_KEY, PUBLIC_KEY)
keynames = CLIENT.password_get_names()
self.assertIn(key, keynames, 'ssh key not in passwords')
CLIENT.password_clear(key)
keynames = CLIENT.password_get_names()
self.assertNotIn(key, keynames, 'ssh key not cleared from passwords')
# check that passwords.yml file size didn't change
size_end = os.path.getsize(pwds_path)
self.assertEqual(size_start, size_end, 'passwords.yml size changed ' +

View File

@ -18,7 +18,7 @@ import signal
import sys
import yaml
from kollacli.common.utils import change_property
from kollacli.common.utils import change_password
def _get_empty_keys(path):
@ -64,17 +64,21 @@ def _password_cmd(argv):
"""password command
args for password command:
-p path # path to passwords.yaml
-k key # key of password
-v value # value of password
-c # flag to clear the password
-l # print to stdout a csv string of the existing keys
-e # get keys of passwords with empty values
-p path # path to passwords.yaml
-k key # key of password
-v value # value of password (if not ssh keys)
-r private key value # ssh private key
-u public key value # ssh public key
-c # flag to clear the password
-l # print to stdout a csv string of the existing keys
-e # get keys of passwords with empty values
"""
opts, _ = getopt.getopt(argv[2:], 'p:k:v:cle')
opts, _ = getopt.getopt(argv[2:], 'p:k:v:r:u:cle')
path = ''
pwd_key = ''
pwd_value = ''
pwd_ssh_private = ''
pwd_ssh_public = ''
clear_flag = False
list_flag = False
empty_flag = False
@ -85,6 +89,10 @@ def _password_cmd(argv):
pwd_key = arg
elif opt == '-v':
pwd_value = arg
elif opt == '-r':
pwd_ssh_private = arg.replace('"', '')
elif opt == '-u':
pwd_ssh_public = arg.replace('"', '')
elif opt == '-c':
clear_flag = True
elif opt == '-l':
@ -98,10 +106,10 @@ def _password_cmd(argv):
# get empty passwords
_get_empty_keys(path)
else:
# edit a password
property_dict = {}
property_dict[pwd_key] = pwd_value
change_property(path, property_dict, clear_flag)
# edit/clear a password
change_password(path, pwd_key, pvalue=pwd_value,
private_key=pwd_ssh_private,
public_key=pwd_ssh_public, clear=clear_flag)
def _job_cmd(argv):