Keyring support for openstackclient.

Bug: 1030440

If password is defined in keyring, use it; otherwise, prompt for the
password. Keying is configured using command line switch,
--os-use-keyring or env(OS_USE_KEYRING).

* openstackclient/common/openstackkeyring.py
  The abstract class for keyring, specifically for openstack. The
  class is used to store encrypted password in keyring, without
  prompting for keyring password. The encrypted password is
  stored in ~/.openstack-keyring.cfg file.

* openstack-common.py
  Update openstackkeyring library from openstack.common.

* openstackclient/shell.py
  OpenStackClient.build_option_parser(): New boolean argument,
   --os-use-keyring, default to env(OS_USE_KEYRING).
  OpenStackClient.authenticate_user(): Get password from keyring,
  if it is defined; otherwise, prompt for the password. If user
  enter a password and keyring is enabled, store it in keyring.
  OpenStackClient.init_keyring_backend(): New method to define
  openstack backend for keyring.
  OpenStackClient.get_password_from_keyring(): New method to
  get password from keyring.
  OpenStackClient.set_password_in_keyring(): New method go set
  password in keyring.

* toos/pip-requires
  Define keyring and pycrypto as one of dependent.

Change-Id: I36d3a63054658c0ef0553d68b38fefbc236930ef
This commit is contained in:
Bhuvan Arumugam 2012-07-08 16:06:32 -07:00
parent 540c4883d6
commit f0cefcc77d
4 changed files with 112 additions and 1 deletions

@ -1,7 +1,7 @@
[DEFAULT] [DEFAULT]
# The list of modules to copy from openstack-common # The list of modules to copy from openstack-common
modules=setup modules=setup,openstackkeyring
# The base module to hold the copy of openstack.common # The base module to hold the copy of openstack.common
base=openstackclient base=openstackclient

@ -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')

@ -29,10 +29,12 @@ from cliff.commandmanager import CommandManager
from openstackclient.common import clientmanager from openstackclient.common import clientmanager
from openstackclient.common import exceptions as exc from openstackclient.common import exceptions as exc
from openstackclient.common import openstackkeyring
from openstackclient.common import utils from openstackclient.common import utils
VERSION = '0.1' VERSION = '0.1'
KEYRING_SERVICE = 'openstack'
def env(*vars, **kwargs): def env(*vars, **kwargs):
@ -123,6 +125,18 @@ class OpenStackShell(App):
default=env('OS_URL'), default=env('OS_URL'),
help='Defaults to 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 return parser
def authenticate_user(self): def authenticate_user(self):
@ -149,12 +163,14 @@ class OpenStackShell(App):
"You must provide a username via" "You must provide a username via"
" either --os-username or env[OS_USERNAME]") " either --os-username or env[OS_USERNAME]")
self.get_password_from_keyring()
if not self.options.os_password: if not self.options.os_password:
# No password, if we've got a tty, try prompting for it # No password, if we've got a tty, try prompting for it
if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty(): if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
# Check for Ctl-D # Check for Ctl-D
try: try:
self.options.os_password = getpass.getpass() self.options.os_password = getpass.getpass()
self.set_password_in_keyring()
except EOFError: except EOFError:
pass pass
# No password because we did't have a tty or the # No password because we did't have a tty or the
@ -188,6 +204,34 @@ class OpenStackShell(App):
) )
return 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): def initialize_app(self, argv):
"""Global app init bits: """Global app init bits:

@ -1,7 +1,9 @@
cliff cliff
argparse argparse
httplib2 httplib2
keyring
prettytable prettytable
pycrypto
python-keystoneclient>=0.1,<0.2 python-keystoneclient>=0.1,<0.2
python-novaclient>=2,<3 python-novaclient>=2,<3
simplejson simplejson