def initialize_key_metadata(key, salt, force_overwrite=False, nested_transaction=False): """ Called when key is first specified to set some database encrypted contents. This must be run before the crypto engine has been initialized with the secret key. :param key: The new encryption and signing key set. :type key: ensconce.crypto.MasterKey :param salt: The salt to use for the KDF function. IMPORTANT: This cannot change w/o re-encrypting database. :type salt: str :param force_overwrite: Whether to delete any existing metadata first (dangerous!) :type force_overwrite: bool :param nested_transaction: Whether this is being run within an existing transaction (i.e. do not commit). :type nested_transaction: bool :raise ensconce.exc.CryptoAlreadyInitialized: If the engine has already been initialized we bail out. :raise ensconce.exc.UnconfiguredModel: If we can't create an SA session. :raise ensconce.exc.ExistingKeyMetadata: If there is already key metadata (and `force_overwrite` param is not `True`). """ assert isinstance(key, MasterKey) assert isinstance(salt, str) if state.initialized: raise exc.CryptoAlreadyInitialized() if meta.Session is None: raise exc.UnconfiguredModel() session = meta.Session() try: existing_keys = session.query(model.KeyMetadata).all() if len(existing_keys) > 0: if force_overwrite: for ek in existing_keys: session.delete(ek) log.warning("Forcibly removing existing metadata: {0}".format(ek)) session.flush() else: raise exc.ExistingKeyMetadata() km = model.KeyMetadata() km.id = 0 # Chosen to be obviously out of auto-increment "range" km.validation = create_key_validation_payload(key=key) km.kdf_salt = salt session.add(km) if not nested_transaction: session.commit() # We are deliberately committing early here else: session.flush() except: if not nested_transaction: # This conditional probably has little effect, since the connection will be in err state anyway # until a rollback is issued. session.rollback() log.exception("Error initializing key metadata") raise
def stop(self): self.stopped.set() for i in range(50): self.threads[:] = [t for t in self.threads if t.is_alive()] if self.threads: time.sleep(0.1) else: break else: log.warning("not all daemons have been joined: %r" % (self.threads,))
def process_edit(self, **kwargs): form = PasswordEditForm(request_params()) if form.validate(): (pw, modified) = passwords.modify(form.password_id.data, username=form.username.data, password=form.password_decrypted.data, description=form.description.data, tags=form.tags.data) auditlog.log(auditlog.CODE_CONTENT_MOD, target=pw, attributes_modified=modified) notify_entity_activity(pw, 'updated') raise cherrypy.HTTPRedirect('/resource/view/{0}'.format(pw.resource_id)) else: log.warning("Form failed validation: {0}".format(form.errors)) return render('password/edit.html', {'form': form})
def process_edit(self, **kwargs): form = ResourceEditForm(request_params()) form.group_ids.choices = [(g.id, g.label) for g in groups.list()] if form.validate(): (resource, modified) = resources.modify(form.resource_id.data, name=form.name.data, addr=form.addr.data, group_ids=form.group_ids.data, notes=form.notes_decrypted.data, description=form.description.data, tags=form.tags.data) # XXX: process auditlog.log(auditlog.CODE_CONTENT_MOD, target=resource, attributes_modified=modified) notify_entity_activity(resource, 'updated') raise cherrypy.HTTPRedirect('/resource/view/{0}'.format(resource.id)) else: log.warning("Form validation failed.") log.warning(form.errors) return render('resource/edit.html', {'form': form})
def from_structure(self, structure): """ Populates the SQLAlchemy model from a python dictionary of the database structure. """ session = meta.Session() try: for resource_s in structure['resources']: log.debug("Importing: {0!r}".format(resource_s)) # First build up a list of group_ids for this resource that will correspond to groups # in *this* database. group_ids = [] for gname in resource_s['groups']: group = groups.get_by_name(gname, assert_exists=False) if not group: group = groups.create(gname) log.info("Created group: {0!r}".format(group)) else: log.info("Found existing group: {0!r}".format(group)) group_ids.append(group.id) # First we should see if there is a match for the id and name; we can't rely on name alone since # there is no guarantee of name uniqueness (even with a group) resource = None resource_candidate = resources.get(resource_s['id'], assert_exists=False) if resource_candidate and resource_candidate.name == resource_s['name']: resource = resource_candidate else: # If we find a matching resource (by name) and there is only one then we'll use that. try: resource = resources.get_by_name(resource_s['name'], assert_single=True, assert_exists=True) except MultipleResultsFound: log.info("Multiple resource matched name {0!r}, will create a new one.".format(resource_s['name'])) except exc.NoSuchEntity: log.debug("No resource found matching name: {0!r}".format(resource_s['name'])) pass resource_attribs = ('name', 'addr', 'description', 'notes', 'tags') resource_attribs_update = dict([(k,v) for (k,v) in resource_s.items() if k in resource_attribs]) if resource: (resource, modified) = resources.modify(resource.id, group_ids=group_ids, **resource_attribs_update) # (yes, we are overwriting 'resource' var with new copy returned from this method) log.info("Updating existing resource: {0!r} (modified: {1!r})".format(resource, modified)) if modified and modified != ['group_ids']: if not self.force: raise RuntimeError("Refusing to modify existing resource attributes {0!r} on {1!r} (use 'force' to override this).".format(modified, resource)) else: log.warning("Overwriting resource attributes {0!r} on {1!r}".format(modified, resource)) else: # We will just assume that we need to create the resource. Yes, it's possible it'll match an existing # one, but better to build a merge tool than end up silently merging things that are not the same. resource = resources.create(group_ids=group_ids, **resource_attribs_update) log.info("Created new resource: {0!r}".format(resource)) # Add the passwords for password_s in resource_s['passwords']: password_attribs = ('username', 'description', 'password', 'tags') password_attribs_update = dict([(k,v) for (k,v) in password_s.items() if k in password_attribs]) # Look for a matching password. We do know that this is unique. password = passwords.get_for_resource(password_s['username'], password_s['resource_id'], assert_exists=False) if password: (password, modified) = passwords.modify(password_id=password.id, **password_attribs_update) # (Yeah, we overwrite password object.) log.info("Updating existing password: {0!r} (modified: {1!r})".format(password, modified)) non_pw_modified = set(modified) - set(['password']) if not modified: log.debug("Password row not modified.") else: log.debug("Password modified: {0!r}".format(modified)) # If anything changed other than password, we need to ensure that force=true if non_pw_modified: if not self.force: raise RuntimeError("Refusing to modify existing password attributes {0!r} on {1!r} (use 'force' to override this).".format(non_pw_modified, password)) else: log.warning("Overwriting password attributes {0!r} on {1!r}".format(non_pw_modified, password)) else: password = passwords.create(resource_id=resource.id, **password_attribs_update) log.info("Creating new password: {0!r}".format(password)) # This probably isn't necessary as all the DAO methods should also flush session, but might as well. session.flush() except: session.rollback() raise