2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright [2010] [Anso Labs, LLC]
5
# Licensed under the Apache License, Version 2.0 (the "License");
6
# you may not use this file except in compliance with the License.
7
# You may obtain a copy of the License at
9
# http://www.apache.org/licenses/LICENSE-2.0
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS,
13
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
# See the License for the specific language governing permissions and
15
# limitations under the License.
18
Nova users and user management, including RBAC hooks.
32
import fakeldap as ldap
35
from nova import datastore
37
# TODO(termie): clean up these imports
39
from nova import exception
40
from nova import flags
41
from nova import crypto
42
from nova import utils
43
import access as simplerbac
45
from nova import objectstore # for flags
49
flags.DEFINE_string('ldap_url', 'ldap://localhost', 'Point this at your ldap server')
50
flags.DEFINE_string('ldap_password', 'changeme', 'LDAP password')
51
flags.DEFINE_string('user_dn', 'cn=Manager,dc=example,dc=com', 'DN of admin user')
52
flags.DEFINE_string('user_unit', 'Users', 'OID for Users')
53
flags.DEFINE_string('ldap_subtree', 'ou=Users,dc=example,dc=com', 'OU for Users')
55
flags.DEFINE_string('ldap_sysadmin',
56
'cn=sysadmins,ou=Groups,dc=example,dc=com', 'OU for Sysadmins')
57
flags.DEFINE_string('ldap_netadmin',
58
'cn=netadmins,ou=Groups,dc=example,dc=com', 'OU for NetAdmins')
59
flags.DEFINE_string('ldap_cloudadmin',
60
'cn=cloudadmins,ou=Groups,dc=example,dc=com', 'OU for Cloud Admins')
61
flags.DEFINE_string('ldap_itsec',
62
'cn=itsec,ou=Groups,dc=example,dc=com', 'OU for ItSec')
64
flags.DEFINE_string('credentials_template',
65
utils.abspath('auth/novarc.template'),
66
'Template for creating users rc file')
67
flags.DEFINE_string('credential_key_file', 'pk.pem',
68
'Filename of private key in credentials zip')
69
flags.DEFINE_string('credential_cert_file', 'cert.pem',
70
'Filename of certificate in credentials zip')
71
flags.DEFINE_string('credential_rc_file', 'novarc',
72
'Filename of rc in credentials zip')
74
_log = logging.getLogger('auth')
75
_log.setLevel(logging.WARN)
79
class UserError(exception.ApiError):
82
class InvalidKeyPair(exception.ApiError):
86
def __init__(self, id, name, access, secret, admin):
87
self.manager = UserManager.instance()
93
self.keeper = datastore.Keeper(prefix="user")
99
def has_role(self, role_type):
100
return self.manager.has_role(self.id, role_type)
102
def is_authorized(self, owner_id, action=None):
103
if self.is_admin() or owner_id == self.id:
107
project = None #(Fixme)
108
target_object = None # (Fixme, should be passed in)
109
return simplerbac.is_allowed(action, self, project, target_object)
111
def get_credentials(self):
112
rc = self.generate_rc()
113
private_key, signed_cert = self.generate_x509_cert()
115
tmpdir = tempfile.mkdtemp()
116
zf = os.path.join(tmpdir, "temp.zip")
117
zippy = zipfile.ZipFile(zf, 'w')
118
zippy.writestr(FLAGS.credential_rc_file, rc)
119
zippy.writestr(FLAGS.credential_key_file, private_key)
120
zippy.writestr(FLAGS.credential_cert_file, signed_cert)
121
zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(self.id))
123
with open(zf, 'rb') as f:
126
shutil.rmtree(tmpdir)
130
def generate_rc(self):
131
rc = open(FLAGS.credentials_template).read()
132
rc = rc % { 'access': self.access,
133
'secret': self.secret,
134
'ec2': FLAGS.ec2_url,
135
's3': 'http://%s:%s' % (FLAGS.s3_host, FLAGS.s3_port),
136
'nova': FLAGS.ca_file,
137
'cert': FLAGS.credential_cert_file,
138
'key': FLAGS.credential_key_file,
142
def generate_key_pair(self, name):
143
return self.manager.generate_key_pair(self.id, name)
145
def generate_x509_cert(self):
146
return self.manager.generate_x509_cert(self.id)
148
def create_key_pair(self, name, public_key, fingerprint):
149
return self.manager.create_key_pair(self.id,
154
def get_key_pair(self, name):
155
return self.manager.get_key_pair(self.id, name)
157
def delete_key_pair(self, name):
158
return self.manager.delete_key_pair(self.id, name)
160
def get_key_pairs(self):
161
return self.manager.get_key_pairs(self.id)
163
class KeyPair(object):
164
def __init__(self, name, owner, public_key, fingerprint):
165
self.manager = UserManager.instance()
168
self.public_key = public_key
169
self.fingerprint = fingerprint
172
return self.manager.delete_key_pair(self.owner, self.name)
174
class UserManager(object):
176
if hasattr(self.__class__, '_instance'):
177
raise Exception('Attempted to instantiate singleton')
181
if not hasattr(cls, '_instance'):
186
inst.create_user('fake', 'fake', 'fake')
189
inst.create_user('user', 'user', 'user')
192
inst.create_user('admin', 'admin', 'admin', True)
196
def authenticate(self, params, signature, verb='GET', server_string='127.0.0.1:8773', path='/'):
197
# TODO: Check for valid timestamp
198
access_key = params['AWSAccessKeyId']
199
user = self.get_user_from_access_key(access_key)
202
# hmac can't handle unicode, so encode ensures that secret isn't unicode
203
expected_signature = signer.Signer(user.secret.encode()).generate(params, verb, server_string, path)
204
_log.debug('user.secret: %s', user.secret)
205
_log.debug('expected_signature: %s', expected_signature)
206
_log.debug('signature: %s', signature)
207
if signature == expected_signature:
210
def has_role(self, user, role, project=None):
211
# Map role to ldap group
212
group = FLAGS.__getitem__("ldap_%s" % role)
213
with LDAPWrapper() as conn:
214
return conn.is_member_of(user, group)
216
def add_role(self, user, role, project=None):
217
# TODO: Project-specific roles
218
group = FLAGS.__getitem__("ldap_%s" % role)
219
with LDAPWrapper() as conn:
220
return conn.add_to_group(user, group)
222
def get_user(self, uid):
223
with LDAPWrapper() as conn:
224
return conn.find_user(uid)
226
def get_user_from_access_key(self, access_key):
227
with LDAPWrapper() as conn:
228
return conn.find_user_by_access_key(access_key)
231
with LDAPWrapper() as conn:
232
return conn.find_users()
234
def create_user(self, uid, access=None, secret=None, admin=False):
235
if access == None: access = str(uuid.uuid4())
236
if secret == None: secret = str(uuid.uuid4())
237
with LDAPWrapper() as conn:
238
u = conn.create_user(uid, access, secret, admin)
241
def delete_user(self, uid):
242
with LDAPWrapper() as conn:
243
conn.delete_user(uid)
245
def generate_key_pair(self, uid, key_name):
246
# generating key pair is slow so delay generation
248
with LDAPWrapper() as conn:
249
if not conn.user_exists(uid):
250
raise UserError("User " + uid + " doesn't exist")
251
if conn.key_pair_exists(uid, key_name):
252
raise InvalidKeyPair("The keypair '" +
256
private_key, public_key, fingerprint = crypto.generate_key_pair()
257
self.create_key_pair(uid, key_name, public_key, fingerprint)
258
return private_key, fingerprint
260
def create_key_pair(self, uid, key_name, public_key, fingerprint):
261
with LDAPWrapper() as conn:
262
return conn.create_key_pair(uid, key_name, public_key, fingerprint)
264
def get_key_pair(self, uid, key_name):
265
with LDAPWrapper() as conn:
266
return conn.find_key_pair(uid, key_name)
268
def get_key_pairs(self, uid):
269
with LDAPWrapper() as conn:
270
return conn.find_key_pairs(uid)
272
def delete_key_pair(self, uid, key_name):
273
with LDAPWrapper() as conn:
274
conn.delete_key_pair(uid, key_name)
276
def get_signed_zip(self, uid):
277
user = self.get_user(uid)
278
return user.get_credentials()
280
def generate_x509_cert(self, uid):
281
(private_key, csr) = crypto.generate_x509_cert(self.__cert_subject(uid))
282
# TODO - This should be async call back to the cloud controller
283
signed_cert = crypto.sign_csr(csr, uid)
284
return (private_key, signed_cert)
286
def sign_cert(self, csr, uid):
287
return crypto.sign_csr(csr, uid)
289
def __cert_subject(self, uid):
290
return "/C=US/ST=California/L=The_Mission/O=AnsoLabs/OU=Nova/CN=%s-%s" % (uid, str(datetime.datetime.utcnow().isoformat()))
293
class LDAPWrapper(object):
295
self.user = FLAGS.user_dn
296
self.passwd = FLAGS.ldap_password
302
def __exit__(self, type, value, traceback):
303
#logging.info('type, value, traceback: %s, %s, %s', type, value, traceback)
308
""" connect to ldap as admin user """
310
self.conn = fakeldap.initialize(FLAGS.ldap_url)
312
assert(ldap.__name__ != 'fakeldap')
313
self.conn = ldap.initialize(FLAGS.ldap_url)
314
self.conn.simple_bind_s(self.user, self.passwd)
316
def find_object(self, dn, query = None):
317
objects = self.find_objects(dn, query)
318
if len(objects) == 0:
322
def find_objects(self, dn, query = None):
324
res = self.conn.search_s(dn, ldap.SCOPE_SUBTREE, query)
327
# just return the attributes
328
return [x[1] for x in res]
330
def find_users(self):
331
attrs = self.find_objects(FLAGS.ldap_subtree, '(objectclass=novaUser)')
332
return [self.__to_user(attr) for attr in attrs]
334
def find_key_pairs(self, uid):
335
dn = 'uid=%s,%s' % (uid, FLAGS.ldap_subtree)
336
attrs = self.find_objects(dn, '(objectclass=novaKeyPair)')
337
return [self.__to_key_pair(uid, attr) for attr in attrs]
339
def find_user(self, name):
340
dn = 'uid=%s,%s' % (name, FLAGS.ldap_subtree)
341
attr = self.find_object(dn, '(objectclass=novaUser)')
342
return self.__to_user(attr)
344
def user_exists(self, name):
345
return self.find_user(name) != None
347
def find_key_pair(self, uid, key_name):
348
dn = 'cn=%s,uid=%s,%s' % (key_name,
351
attr = self.find_object(dn, '(objectclass=novaKeyPair)')
352
return self.__to_key_pair(uid, attr)
354
def delete_key_pairs(self, uid):
355
keys = self.find_key_pairs(uid)
358
self.delete_key_pair(uid, key.name)
360
def key_pair_exists(self, uid, key_name):
361
return self.find_key_pair(uid, key_name) != None
363
def create_user(self, name, access_key, secret_key, is_admin):
364
if self.user_exists(name):
365
raise UserError("LDAP user " + name + " already exists")
367
('objectclass', ['person',
368
'organizationalPerson',
371
('ou', [FLAGS.user_unit]),
375
('secretKey', [secret_key]),
376
('accessKey', [access_key]),
377
('isAdmin', [str(is_admin).upper()]),
379
self.conn.add_s('uid=%s,%s' % (name, FLAGS.ldap_subtree),
381
return self.__to_user(dict(attr))
383
def create_project(self, name, project_manager):
384
# PM can be user object or string containing DN
387
def is_member_of(self, name, group):
390
def add_to_group(self, name, group):
393
def remove_from_group(self, name, group):
396
def create_key_pair(self, uid, key_name, public_key, fingerprint):
397
"""create's a public key in the directory underneath the user"""
398
# TODO(vish): possibly refactor this to store keys in their own ou
399
# and put dn reference in the user object
401
('objectclass', ['novaKeyPair']),
403
('sshPublicKey', [public_key]),
404
('keyFingerprint', [fingerprint]),
406
self.conn.add_s('cn=%s,uid=%s,%s' % (key_name,
410
return self.__to_key_pair(uid, dict(attr))
412
def find_user_by_access_key(self, access):
413
query = '(' + 'accessKey' + '=' + access + ')'
414
dn = FLAGS.ldap_subtree
415
return self.__to_user(self.find_object(dn, query))
417
def delete_key_pair(self, uid, key_name):
418
if not self.key_pair_exists(uid, key_name):
419
raise UserError("Key Pair " +
421
" doesn't exist for user " +
423
self.conn.delete_s('cn=%s,uid=%s,%s' % (key_name, uid,
426
def delete_user(self, name):
427
if not self.user_exists(name):
428
raise UserError("User " +
431
self.delete_key_pairs(name)
432
self.conn.delete_s('uid=%s,%s' % (name,
435
def __to_user(self, attr):
440
name = attr['uid'][0],
441
access = attr['accessKey'][0],
442
secret = attr['secretKey'][0],
443
admin = (attr['isAdmin'][0] == 'TRUE')
446
def __to_key_pair(self, owner, attr):
451
name = attr['cn'][0],
452
public_key = attr['sshPublicKey'][0],
453
fingerprint = attr['keyFingerprint'][0],