~ntt-pf-lab/nova/monkey_patch_notification

« back to all changes in this revision

Viewing changes to nova/auth/users.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
3
# Copyright [2010] [Anso Labs, LLC]
 
4
#
 
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
 
8
#
 
9
#        http://www.apache.org/licenses/LICENSE-2.0
 
10
#
 
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.
 
16
 
 
17
"""
 
18
Nova users and user management, including RBAC hooks.
 
19
"""
 
20
 
 
21
import datetime
 
22
import logging
 
23
import os
 
24
import shutil
 
25
import tempfile
 
26
import uuid
 
27
import zipfile
 
28
 
 
29
try:
 
30
    import ldap
 
31
except Exception, e:
 
32
    import fakeldap as ldap
 
33
 
 
34
import fakeldap
 
35
from nova import datastore
 
36
 
 
37
# TODO(termie): clean up these imports
 
38
import signer
 
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
 
44
 
 
45
from nova import objectstore # for flags
 
46
 
 
47
FLAGS = flags.FLAGS
 
48
 
 
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')
 
54
 
 
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')
 
63
 
 
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')
 
73
 
 
74
_log = logging.getLogger('auth')
 
75
_log.setLevel(logging.WARN)
 
76
 
 
77
 
 
78
 
 
79
class UserError(exception.ApiError):
 
80
    pass
 
81
 
 
82
class InvalidKeyPair(exception.ApiError):
 
83
    pass
 
84
 
 
85
class User(object):
 
86
    def __init__(self, id, name, access, secret, admin):
 
87
        self.manager = UserManager.instance()
 
88
        self.id = id
 
89
        self.name = name
 
90
        self.access = access
 
91
        self.secret = secret
 
92
        self.admin = admin
 
93
        self.keeper = datastore.Keeper(prefix="user")
 
94
 
 
95
 
 
96
    def is_admin(self):
 
97
        return self.admin
 
98
 
 
99
    def has_role(self, role_type):
 
100
        return self.manager.has_role(self.id, role_type)
 
101
 
 
102
    def is_authorized(self, owner_id, action=None):
 
103
        if self.is_admin() or owner_id == self.id:
 
104
            return True
 
105
        if action == None:
 
106
            return False
 
107
        project = None #(Fixme)
 
108
        target_object = None # (Fixme, should be passed in)
 
109
        return simplerbac.is_allowed(action, self, project, target_object)
 
110
 
 
111
    def get_credentials(self):
 
112
        rc = self.generate_rc()
 
113
        private_key, signed_cert = self.generate_x509_cert()
 
114
 
 
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))
 
122
        zippy.close()
 
123
        with open(zf, 'rb') as f:
 
124
            buffer = f.read()
 
125
 
 
126
        shutil.rmtree(tmpdir)
 
127
        return buffer
 
128
 
 
129
 
 
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,
 
139
            }
 
140
        return rc
 
141
 
 
142
    def generate_key_pair(self, name):
 
143
        return self.manager.generate_key_pair(self.id, name)
 
144
 
 
145
    def generate_x509_cert(self):
 
146
        return self.manager.generate_x509_cert(self.id)
 
147
 
 
148
    def create_key_pair(self, name, public_key, fingerprint):
 
149
        return self.manager.create_key_pair(self.id,
 
150
                                            name,
 
151
                                            public_key,
 
152
                                            fingerprint)
 
153
 
 
154
    def get_key_pair(self, name):
 
155
        return self.manager.get_key_pair(self.id, name)
 
156
 
 
157
    def delete_key_pair(self, name):
 
158
        return self.manager.delete_key_pair(self.id, name)
 
159
 
 
160
    def get_key_pairs(self):
 
161
        return self.manager.get_key_pairs(self.id)
 
162
 
 
163
class KeyPair(object):
 
164
    def __init__(self, name, owner, public_key, fingerprint):
 
165
        self.manager = UserManager.instance()
 
166
        self.owner = owner
 
167
        self.name = name
 
168
        self.public_key = public_key
 
169
        self.fingerprint = fingerprint
 
170
 
 
171
    def delete(self):
 
172
        return self.manager.delete_key_pair(self.owner, self.name)
 
173
 
 
174
class UserManager(object):
 
175
    def __init__(self):
 
176
        if hasattr(self.__class__, '_instance'):
 
177
            raise Exception('Attempted to instantiate singleton')
 
178
 
 
179
    @classmethod
 
180
    def instance(cls):
 
181
        if not hasattr(cls, '_instance'):
 
182
            inst = UserManager()
 
183
            cls._instance = inst
 
184
            if FLAGS.fake_users:
 
185
                try:
 
186
                    inst.create_user('fake', 'fake', 'fake')
 
187
                except: pass
 
188
                try:
 
189
                    inst.create_user('user', 'user', 'user')
 
190
                except: pass
 
191
                try:
 
192
                    inst.create_user('admin', 'admin', 'admin', True)
 
193
                except: pass
 
194
        return cls._instance
 
195
 
 
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)
 
200
        if user == None:
 
201
            return None
 
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:
 
208
            return user
 
209
 
 
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)
 
215
 
 
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)
 
221
 
 
222
    def get_user(self, uid):
 
223
        with LDAPWrapper() as conn:
 
224
            return conn.find_user(uid)
 
225
 
 
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)
 
229
 
 
230
    def get_users(self):
 
231
        with LDAPWrapper() as conn:
 
232
            return conn.find_users()
 
233
 
 
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)
 
239
        return u
 
240
 
 
241
    def delete_user(self, uid):
 
242
        with LDAPWrapper() as conn:
 
243
            conn.delete_user(uid)
 
244
 
 
245
    def generate_key_pair(self, uid, key_name):
 
246
        # generating key pair is slow so delay generation
 
247
        # until after check
 
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 '" +
 
253
                            key_name +
 
254
                            "' already exists.",
 
255
                            "Duplicate")
 
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
 
259
 
 
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)
 
263
 
 
264
    def get_key_pair(self, uid, key_name):
 
265
        with LDAPWrapper() as conn:
 
266
            return conn.find_key_pair(uid, key_name)
 
267
 
 
268
    def get_key_pairs(self, uid):
 
269
        with LDAPWrapper() as conn:
 
270
            return conn.find_key_pairs(uid)
 
271
 
 
272
    def delete_key_pair(self, uid, key_name):
 
273
        with LDAPWrapper() as conn:
 
274
            conn.delete_key_pair(uid, key_name)
 
275
 
 
276
    def get_signed_zip(self, uid):
 
277
        user = self.get_user(uid)
 
278
        return user.get_credentials()
 
279
 
 
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)
 
285
 
 
286
    def sign_cert(self, csr, uid):
 
287
        return crypto.sign_csr(csr, uid)
 
288
 
 
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()))
 
291
 
 
292
 
 
293
class LDAPWrapper(object):
 
294
    def __init__(self):
 
295
        self.user = FLAGS.user_dn
 
296
        self.passwd = FLAGS.ldap_password
 
297
 
 
298
    def __enter__(self):
 
299
        self.connect()
 
300
        return self
 
301
 
 
302
    def __exit__(self, type, value, traceback):
 
303
        #logging.info('type, value, traceback: %s, %s, %s', type, value, traceback)
 
304
        self.conn.unbind_s()
 
305
        return False
 
306
 
 
307
    def connect(self):
 
308
        """ connect to ldap as admin user """
 
309
        if FLAGS.fake_users:
 
310
            self.conn = fakeldap.initialize(FLAGS.ldap_url)
 
311
        else:
 
312
            assert(ldap.__name__ != 'fakeldap')
 
313
            self.conn = ldap.initialize(FLAGS.ldap_url)
 
314
        self.conn.simple_bind_s(self.user, self.passwd)
 
315
 
 
316
    def find_object(self, dn, query = None):
 
317
        objects = self.find_objects(dn, query)
 
318
        if len(objects) == 0:
 
319
            return None
 
320
        return objects[0]
 
321
 
 
322
    def find_objects(self, dn, query = None):
 
323
        try:
 
324
            res = self.conn.search_s(dn, ldap.SCOPE_SUBTREE, query)
 
325
        except Exception:
 
326
            return []
 
327
        # just return the attributes
 
328
        return [x[1] for x in res]
 
329
 
 
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]
 
333
 
 
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]
 
338
 
 
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)
 
343
 
 
344
    def user_exists(self, name):
 
345
        return self.find_user(name) != None
 
346
 
 
347
    def find_key_pair(self, uid, key_name):
 
348
        dn = 'cn=%s,uid=%s,%s' % (key_name,
 
349
                                   uid,
 
350
                                   FLAGS.ldap_subtree)
 
351
        attr = self.find_object(dn, '(objectclass=novaKeyPair)')
 
352
        return self.__to_key_pair(uid, attr)
 
353
 
 
354
    def delete_key_pairs(self, uid):
 
355
        keys = self.find_key_pairs(uid)
 
356
        if keys != None:
 
357
            for key in keys:
 
358
                self.delete_key_pair(uid, key.name)
 
359
 
 
360
    def key_pair_exists(self, uid, key_name):
 
361
        return self.find_key_pair(uid, key_name) != None
 
362
 
 
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")
 
366
        attr = [
 
367
            ('objectclass', ['person',
 
368
                             'organizationalPerson',
 
369
                             'inetOrgPerson',
 
370
                             'novaUser']),
 
371
            ('ou', [FLAGS.user_unit]),
 
372
            ('uid', [name]),
 
373
            ('sn', [name]),
 
374
            ('cn', [name]),
 
375
            ('secretKey', [secret_key]),
 
376
            ('accessKey', [access_key]),
 
377
            ('isAdmin', [str(is_admin).upper()]),
 
378
        ]
 
379
        self.conn.add_s('uid=%s,%s' % (name, FLAGS.ldap_subtree),
 
380
                        attr)
 
381
        return self.__to_user(dict(attr))
 
382
 
 
383
    def create_project(self, name, project_manager):
 
384
        # PM can be user object or string containing DN
 
385
        pass
 
386
 
 
387
    def is_member_of(self, name, group):
 
388
        return True
 
389
 
 
390
    def add_to_group(self, name, group):
 
391
        pass
 
392
 
 
393
    def remove_from_group(self, name, group):
 
394
        pass
 
395
 
 
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
 
400
        attr = [
 
401
            ('objectclass', ['novaKeyPair']),
 
402
            ('cn', [key_name]),
 
403
            ('sshPublicKey', [public_key]),
 
404
            ('keyFingerprint', [fingerprint]),
 
405
        ]
 
406
        self.conn.add_s('cn=%s,uid=%s,%s' % (key_name,
 
407
                                             uid,
 
408
                                             FLAGS.ldap_subtree),
 
409
                                             attr)
 
410
        return self.__to_key_pair(uid, dict(attr))
 
411
 
 
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))
 
416
 
 
417
    def delete_key_pair(self, uid, key_name):
 
418
        if not self.key_pair_exists(uid, key_name):
 
419
            raise UserError("Key Pair " +
 
420
                                    key_name +
 
421
                                    " doesn't exist for user " +
 
422
                                    uid)
 
423
        self.conn.delete_s('cn=%s,uid=%s,%s' % (key_name, uid,
 
424
                                          FLAGS.ldap_subtree))
 
425
 
 
426
    def delete_user(self, name):
 
427
        if not self.user_exists(name):
 
428
            raise UserError("User " +
 
429
                                    name +
 
430
                                    " doesn't exist")
 
431
        self.delete_key_pairs(name)
 
432
        self.conn.delete_s('uid=%s,%s' % (name,
 
433
                                          FLAGS.ldap_subtree))
 
434
 
 
435
    def __to_user(self, attr):
 
436
        if attr == None:
 
437
            return None
 
438
        return User(
 
439
            id = attr['uid'][0],
 
440
            name = attr['uid'][0],
 
441
            access = attr['accessKey'][0],
 
442
            secret = attr['secretKey'][0],
 
443
            admin = (attr['isAdmin'][0] == 'TRUE')
 
444
        )
 
445
 
 
446
    def __to_key_pair(self, owner, attr):
 
447
        if attr == None:
 
448
            return None
 
449
        return KeyPair(
 
450
            owner = owner,
 
451
            name = attr['cn'][0],
 
452
            public_key = attr['sshPublicKey'][0],
 
453
            fingerprint = attr['keyFingerprint'][0],
 
454
        )