diff --git a/openstack-common.conf b/openstack-common.conf index 08e091b17..6b8b86a0c 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=setup +modules=setup,openstackkeyring # The base module to hold the copy of openstack.common base=openstackclient diff --git a/openstackclient/common/openstackkeyring.py b/openstackclient/common/openstackkeyring.py new file mode 100644 index 000000000..3a5ce27f8 --- /dev/null +++ b/openstackclient/common/openstackkeyring.py @@ -0,0 +1,65 @@ +# Copyright 2011 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +""" +Keyring backend for Openstack, to store encrypted password in a file. +""" + +from Crypto.Cipher import AES + +import crypt +import keyring +import os + +KEYRING_FILE = os.path.join(os.path.expanduser('~'), '.openstack-keyring.cfg') + + +class OpenstackKeyring(keyring.backend.BasicFileKeyring): + """ Openstack Keyring to store encrypted password """ + + filename = KEYRING_FILE + + def supported(self): + """ applicable for all platforms, but not recommend """ + pass + + def _init_crypter(self): + """ initialize the crypter using the class name """ + block_size = 32 + padding = '0' + + # init the cipher with the class name, upto block_size + password = __name__[block_size:] + password = password + (block_size - len(password) % \ + block_size) * padding + return AES.new(password, AES.MODE_CFB) + + def encrypt(self, password): + """ encrypt the given password """ + crypter = self._init_crypter() + return crypter.encrypt(password) + + def decrypt(self, password_encrypted): + """ decrypt the given password """ + crypter = self._init_crypter() + return crypter.decrypt(password_encrypted) + + +def os_keyring(): + """ initialize the openstack keyring """ + return keyring.core.load_keyring(None, + 'openstackclient.common.openstackkeyring.OpenstackKeyring') diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 3d0adf993..531ac258d 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -29,10 +29,12 @@ from cliff.commandmanager import CommandManager from openstackclient.common import clientmanager from openstackclient.common import exceptions as exc +from openstackclient.common import openstackkeyring from openstackclient.common import utils VERSION = '0.1' +KEYRING_SERVICE = 'openstack' def env(*vars, **kwargs): @@ -123,6 +125,18 @@ class OpenStackShell(App): default=env('OS_URL'), help='Defaults to env[OS_URL]') + env_os_keyring = env('OS_USE_KEYRING', default=False) + if type(env_os_keyring) == str: + if env_os_keyring.lower() in ['true', '1']: + env_os_keyring = True + else: + env_os_keyring = False + parser.add_argument('--os-use-keyring', + default=env_os_keyring, + action='store_true', + help='Use keyring to store password, ' + 'default=False (Env: OS_USE_KEYRING)') + return parser def authenticate_user(self): @@ -149,12 +163,14 @@ class OpenStackShell(App): "You must provide a username via" " either --os-username or env[OS_USERNAME]") + self.get_password_from_keyring() if not self.options.os_password: # No password, if we've got a tty, try prompting for it if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty(): # Check for Ctl-D try: self.options.os_password = getpass.getpass() + self.set_password_in_keyring() except EOFError: pass # No password because we did't have a tty or the @@ -188,6 +204,34 @@ class OpenStackShell(App): ) return + def init_keyring_backend(self): + """Initialize openstack backend to use for keyring""" + return openstackkeyring.os_keyring() + + def get_password_from_keyring(self): + """Get password from keyring, if it's set""" + if self.options.os_use_keyring: + service = KEYRING_SERVICE + backend = self.init_keyring_backend() + if not self.options.os_password: + password = backend.get_password(service, + self.options.os_username) + self.options.os_password = password + + def set_password_in_keyring(self): + """Set password in keyring for this user""" + if self.options.os_use_keyring: + service = KEYRING_SERVICE + backend = self.init_keyring_backend() + if self.options.os_password: + password = backend.get_password(service, + self.options.os_username) + # either password is not set in keyring, or it is different + if password != self.options.os_password: + backend.set_password(service, + self.options.os_username, + self.options.os_password) + def initialize_app(self, argv): """Global app init bits: diff --git a/tools/pip-requires b/tools/pip-requires index efcaf7fe0..d4ad68b62 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,7 +1,9 @@ cliff argparse httplib2 +keyring prettytable +pycrypto python-keystoneclient>=0.1,<0.2 python-novaclient>=2,<3 simplejson