import re 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.NovaTable import NovaObject, NovaTable class Key(NovaObject): """ 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' join_tables = ['person_key', 'peer_key'] fields = { 'id': Parameter(str, "Key identifier", primary_key=True), #'key_type': Parameter(str, "Key type"), 'public_key': Parameter(str, "Key string", max = 4096), 'name': Parameter(str, "Key name",) } def sync(self, insert = False, validate = True): NovaObject.sync(self, insert, validate) if insert == True or 'id' not in self: self.object = self.api.client_shell.nova.keypairs.create(self.id, self.key) else: self.object = self.api.client_shell.nova.keypairs.update(self.id, dict(self)) def delete(self): assert 'id' in self self.api.client_shell.nova.keypairs.delete(self.id) def validate_public_key(self, key): # Key must not be blacklisted pass return key def validate_name(self, name): keys = Keys(self.api, name) if keys: raise PLCInvalidArgument, "Key name alredy in use" def validate(self): # Basic validation NovaObject.validate(self) assert 'public_key' in self key = self['public_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 # Get all matching keys rows = self.api.db.selectall("SELECT key_id FROM keys WHERE key = %(key)s", self) key_ids = [row['key_id'] for row in rows] assert key_ids assert self['key_id'] in key_ids # Keep the keys in the table self.api.db.do("UPDATE keys SET is_blacklisted = True" \ " WHERE key_id IN (%s)" % ", ".join(map(str, key_ids))) # But disassociate them from all join tables for table in self.join_tables: self.api.db.do("DELETE FROM %s WHERE key_id IN (%s)" % \ (table, ", ".join(map(str, key_ids)))) if commit: self.api.db.commit() class Keys(NovaTable): """ Representation of row(s) from the keys table in the database. """ def __init__(self, api, key_filter = None, columns = None): self.api = api keysManager = self.api.client_shell.nova.keypairs keyPairs = [] if key_filter is not None: if isinstance(key_filter, (list, tuple, set, int, long)): keyPairs = filter(lambda kp: kp.uuid in key_filter, keysManager.findall()) elif isinstance(key_filter, dict): keyPairs = keysManager.findall(**key_filter) elif isinstnace(key_filter, StringTypes): keyPairs = keyManagers.findall(uuid = key_filter) else: raise PLCInvalidArgument, "Wrong key filter %r"%key_filter self.extend(keyPairs)