Skip to content

Commit a5aa06c

Browse files
Jenkinsopenstack-gerrit
authored andcommitted
Merge "Keyring support for openstackclient."
2 parents 5bf1712 + f0cefcc commit a5aa06c

File tree

4 files changed

+112
-1
lines changed

4 files changed

+112
-1
lines changed

openstack-common.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[DEFAULT]
22

33
# The list of modules to copy from openstack-common
4-
modules=setup
4+
modules=setup,openstackkeyring
55

66
# The base module to hold the copy of openstack.common
77
base=openstackclient
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright 2011 OpenStack LLC.
2+
# All Rights Reserved
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
5+
# not use this file except in compliance with the License. You may obtain
6+
# a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
# License for the specific language governing permissions and limitations
14+
# under the License.
15+
#
16+
# vim: tabstop=4 shiftwidth=4 softtabstop=4
17+
18+
"""
19+
Keyring backend for Openstack, to store encrypted password in a file.
20+
"""
21+
22+
from Crypto.Cipher import AES
23+
24+
import crypt
25+
import keyring
26+
import os
27+
28+
KEYRING_FILE = os.path.join(os.path.expanduser('~'), '.openstack-keyring.cfg')
29+
30+
31+
class OpenstackKeyring(keyring.backend.BasicFileKeyring):
32+
""" Openstack Keyring to store encrypted password """
33+
34+
filename = KEYRING_FILE
35+
36+
def supported(self):
37+
""" applicable for all platforms, but not recommend """
38+
pass
39+
40+
def _init_crypter(self):
41+
""" initialize the crypter using the class name """
42+
block_size = 32
43+
padding = '0'
44+
45+
# init the cipher with the class name, upto block_size
46+
password = __name__[block_size:]
47+
password = password + (block_size - len(password) % \
48+
block_size) * padding
49+
return AES.new(password, AES.MODE_CFB)
50+
51+
def encrypt(self, password):
52+
""" encrypt the given password """
53+
crypter = self._init_crypter()
54+
return crypter.encrypt(password)
55+
56+
def decrypt(self, password_encrypted):
57+
""" decrypt the given password """
58+
crypter = self._init_crypter()
59+
return crypter.decrypt(password_encrypted)
60+
61+
62+
def os_keyring():
63+
""" initialize the openstack keyring """
64+
return keyring.core.load_keyring(None,
65+
'openstackclient.common.openstackkeyring.OpenstackKeyring')

openstackclient/shell.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@
2929

3030
from openstackclient.common import clientmanager
3131
from openstackclient.common import exceptions as exc
32+
from openstackclient.common import openstackkeyring
3233
from openstackclient.common import utils
3334

3435

3536
VERSION = '0.1'
37+
KEYRING_SERVICE = 'openstack'
3638

3739

3840
def env(*vars, **kwargs):
@@ -123,6 +125,18 @@ def build_option_parser(self, description, version):
123125
default=env('OS_URL'),
124126
help='Defaults to env[OS_URL]')
125127

128+
env_os_keyring = env('OS_USE_KEYRING', default=False)
129+
if type(env_os_keyring) == str:
130+
if env_os_keyring.lower() in ['true', '1']:
131+
env_os_keyring = True
132+
else:
133+
env_os_keyring = False
134+
parser.add_argument('--os-use-keyring',
135+
default=env_os_keyring,
136+
action='store_true',
137+
help='Use keyring to store password, '
138+
'default=False (Env: OS_USE_KEYRING)')
139+
126140
return parser
127141

128142
def authenticate_user(self):
@@ -149,12 +163,14 @@ def authenticate_user(self):
149163
"You must provide a username via"
150164
" either --os-username or env[OS_USERNAME]")
151165

166+
self.get_password_from_keyring()
152167
if not self.options.os_password:
153168
# No password, if we've got a tty, try prompting for it
154169
if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
155170
# Check for Ctl-D
156171
try:
157172
self.options.os_password = getpass.getpass()
173+
self.set_password_in_keyring()
158174
except EOFError:
159175
pass
160176
# No password because we did't have a tty or the
@@ -188,6 +204,34 @@ def authenticate_user(self):
188204
)
189205
return
190206

207+
def init_keyring_backend(self):
208+
"""Initialize openstack backend to use for keyring"""
209+
return openstackkeyring.os_keyring()
210+
211+
def get_password_from_keyring(self):
212+
"""Get password from keyring, if it's set"""
213+
if self.options.os_use_keyring:
214+
service = KEYRING_SERVICE
215+
backend = self.init_keyring_backend()
216+
if not self.options.os_password:
217+
password = backend.get_password(service,
218+
self.options.os_username)
219+
self.options.os_password = password
220+
221+
def set_password_in_keyring(self):
222+
"""Set password in keyring for this user"""
223+
if self.options.os_use_keyring:
224+
service = KEYRING_SERVICE
225+
backend = self.init_keyring_backend()
226+
if self.options.os_password:
227+
password = backend.get_password(service,
228+
self.options.os_username)
229+
# either password is not set in keyring, or it is different
230+
if password != self.options.os_password:
231+
backend.set_password(service,
232+
self.options.os_username,
233+
self.options.os_password)
234+
191235
def initialize_app(self, argv):
192236
"""Global app init bits:
193237

tools/pip-requires

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
cliff
22
argparse
33
httplib2
4+
keyring
45
prettytable
6+
pycrypto
57
python-keystoneclient>=0.1,<0.2
68
python-novaclient>=2,<3
79
simplejson

0 commit comments

Comments
 (0)