import re import random import string from types import StringTypes from PLC.Faults import * from PLC.Parameter import Parameter from PLC.Filter import Filter from PLC.Debug import profile from PLC.Table import Row, Table from PLC.KeyTypes import KeyType, KeyTypes from PLC.Storage.AlchemyObject import AlchemyObj class Key(AlchemyObj): """ Representation of a row in the keys table. To use, instantiate with a dict of values. Update as you would a dict. Commit to the database with sync(). """ tablename = 'keys' fields = { 'key_id': Parameter(str, "Key identifier", primary_key=True), 'key_type': Parameter(str, "Key type"), 'key': Parameter(str, "Key string", max = 4096), # backwards compatibility 'person_id': Parameter(int, "User to which this key belongs", nullok = True), 'is_blacklisted': Parameter(bool, "Is the key blacklisted", default=False), 'name': Parameter(str, "Key name"), 'peer_id': Parameter(int, "Peer to which this key belongs", nullok = True), 'peer_key_id': Parameter(int, "Foreign key identifier at peer", nullok = True), } def sync(self, commit = True, validate = True): from PLC.Persons import Person # sync the nova record and the plc record assert 'key' in self AlchemyObj.sync(self, commit=commit, validate=validate) persons = Person().select(filter={'person_id': self['person_id']}) if not persons: raise PLCInvalidArgument, "Invalid person id" person = persons[0] if 'key_id' not in self: rand = ''.join(random.choice(string.ascii_letters) for x in range(5)) self['name'] = person.email.split('@')[0] + rand nova_key = { 'public_key': self['key'], 'name': self['name'] } self.object = self.api.client_shell.nova.keypairs.create(**nova_key) AlchemyObj.insert(self, dict(self)) key = Key().select(filter={'name': self['name']})[0] self['key_id'] = key.key_id else: # nova has no way to update existing keys. Just remove the old # key and add a new one nova_key = { 'public_key': self['key'], 'name': self['name'] } self.api.client_shell.nova.keypairs.delete(self['name']) self.object = self.api.client_shell.nova.keypairs.create(**nova_key) AlchemyObj.update(self, {'key_id': self['key_id']}, dict(self)) def delete(self): assert 'key_id' in self assert 'name' in self self.api.client_shell.nova.keypairs.delete(self['name']) AlchemyObj.delete(self, filter={'key_id': self['key_id']}) def validate_key_type(self, key_type): exists = KeyTypes(self.api, key_type) if not exists: raise PLCInvalidArgument, "Invalid key type" return key_type def validate_key(self, key): # Key must not be blacklisted keys = Keys(self.api, {'key': key, 'is_blacklisted': True}) if keys: raise PLCInvalidArgument, "Key is blacklisted and cannot be used" return key def validate(self): # Basic validation AlchemyObj.validate(self) assert 'key' in self key = self['key'] if self['key_type'] == 'ssh': # Accept only SSH version 2 keys without options. From # sshd(8): # # Each protocol version 2 public key consists of: options, # keytype, base64 encoded key, comment. The options field # is optional...The comment field is not used for anything # (but may be convenient for the user to identify the # key). For protocol version 2 the keytype is ``ssh-dss'' # or ``ssh-rsa''. good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$' if not re.match(good_ssh_key, key, re.IGNORECASE): raise PLCInvalidArgument, "Invalid SSH version 2 public key" def blacklist(self, commit = True): """ Permanently blacklist key (and all other identical keys), preventing it from ever being added again. Because this could affect multiple keys associated with multiple accounts, it should be admin only. """ assert 'key_id' in self assert 'key' in self AlchemyObj.updatedb(self, filter={'key': self['key']}, values={'is_blacklisted': True}) class Keys(list): """ Representation of row(s) from the keys table in the database. """ def __init__(self, api, key_filter = None, columns = None): self.api = api if not key_filter: keys = Key().select() elif isinstance(key_filter, dict): keys = Key().select(filter=key_filter) elif isinstance(key_filter, StringTypes): keys = Key().select(filter={'name': key_filter}) elif isinstance(key_filter, int): keys = Key().select(filter={'key_id': key_filter}) elif isinstance(key_filter, (list, tuple, set)): ints = filter(lambda x: isinstance(x, (int, long)), key_filter) strs = filter(lambda x: isinstance(x, StringTypes), key_filter) key_filter = {'key_id': ints, 'name': strs} keys = Key().select(filter=key_filter) else: raise PLCInvalidArgument, "Wrong key filter %s" % key_filter for key in keys: key = Key(api, object=key, columns=columns) self.append(key)