def call_read_write_update_option(self, name, auth, jump_server=None, **kwargs): """Update secrets for an existing named authority. Args: name (str): The name of the authority to lookup. auth (str): Authentification method. Raises: Exception: If named authority does not exists. Outputs: stdout: Confirm message with human-readable authority details. """ Log.debug("Incoming update request for named authority {n}", n=name) if not self.get_db().exists(name): Log.debug("Named authority is not found...") error = "Cannot update entry: \"{name}\" not found in " \ "keychain (missing key)" Log.fatal(error, name=name) if jump_server is not None: Log.debug("Update requests to change jump server...") jump_auth = self.build_authority_from_signature(jump_server) self.get_db().update_jump_auth(name, jump_auth) Log.debug("New jump set to authority: {a}", a=str(jump_auth)) passkey = Passkey.resolve(auth) self.get_db().update_passkey(name, passkey) Log.debug("New passkey set ... ") Display.show_update(self.get_db().fetch_auth(name))
def build_random_name(self): """Generate a random name with 16 characters. Returns: str: Random 16 characters. """ name = uuid4().hex[:16] Log.debug("Generating new random name: {n}", n=name) return name
def initialize(cls, secrets): """Register keychain to manager's database. Args: secrets (object): Dict-like object storage. """ cls.__secrets = Keychain(secrets) cls.__database = Database(cls.__secrets) Log.debug("Manager initialized...")
def call_read_only_recall_option(self, signature, **kwargs): """Lookup wrapper without explicit authority. Finds authority by signature and calls "lookup" for passkey. Args: key (str): Possible authority signature to match or name. Raises: Exception: If named authority does not exists. Outputs: stdout: Human-readable passkey with authority details and hostname. """ Log.debug("Searching secret passkey for key {s}", s=signature) if len(signature) < self.MIN_NAME_LEN: Log.debug("Trying key as authority signature...") for name, auth, _, _ in self.get_db().query_all(): if auth.signature() == signature: Log.debug("Got authority with signature {s}", s=signature) signature = name break Log.debug("Running a lookup for named authority: {n}", n=signature) self.call_read_only_lookup_option(signature)
def call_secret_read_stdout_dump_option(self, name, signature, **kwargs): """Vulnerable passkey dump to stdout. Args: name (str): The name of the authority to lookup. signature (str): The signature for an authority to find its name. Outputs: stdout: Base64 encoded passkey. Raises: Exception: If unsupported passkey is found. """ def print_passkey(auth_name): _, _, secret = self.get_db().lookup(auth_name) return "{}\n{}".format(*Passkey.copy(secret)) passkey = "" # dummy passkey Log.debug("Incoming secret dump request...") if len(name) > 0: Log.debug("Got named authority {n}...", n=name) passkey = print_passkey(name) elif len(signature) > 0: for each, name in self.get_db().query_auth(): if each.signature() != signature: continue Log.debug("Records matched authority, got passkey...") passkey = print_passkey(name) break # exit on first match Log.debug("Printing passkey..." if passkey else "Nothing to print...") Display.show_dump(passkey)
def call_read_write_forget_option(self, signature, **kwargs): """Remove wrapper without explicit authority. Finds authority by signature and calls "remove" for passkey. Args: key (str): Possible authority signature to match or name. Raises: Exception: If named authority does not exists. Outputs: stdout: Human-readable passkey with authority details and hostname. """ Log.debug("Testing if key {s} exists", s=signature) if len(signature) < self.MIN_NAME_LEN: Log.debug("Trying key as authority signature...") for name, auth, _, _ in self.get_db().query_all(): if auth.signature() == signature: Log.debug("Got authority with signature {s}", s=signature) signature = name break Log.debug("Running cleanup after named authority: {n}", n=signature) self.call_read_write_remove_option(signature)
def unlocker(secrets, args=()): """Manage unlocker's keychain. Can add, edit, delete and lookup keys. All keys stored are compressed and encoded. """ # register secrets on keychain Manager.initialize(secrets) Log.debug("Preparing to boot...") # initialize manager and parse arguments mng = Manager(*args) mng.call() Log.debug("Preparing to exit...")
def main(): """Main callable function. Configure logging capabilities, read input from shell or stdin (can exit fastly) and run unlocker manager. """ # configure log Log.configure() Log.debug("Running in debug mode...") # read input args = read_input() # run unlocker with input args unlocker(args=args)
def call_read_write_migrate_option(self, *args, **kwargs): """Migration wrapper. Determine if action is to import or to export and launch process. Raises: Exception: If something goes terrible wrong. Inputs: stdin: Compressed import-ready secrets. Outputs: stdout: Compressed exported stored secrets. """ Log.debug("Incoming migrate request...") Migrate.discover(self) Log.debug("Migrated data...")
def read_input(): """Read input from stdin or shell. Some shell arguments can trigger exit before reaching end of function. """ # check if data is piped to unlocked and dump passkey if StreamData.read(): Log.debug("Reading stdin: {data}", data=StreamData.buf_in) return StreamData.OPTION, StreamData.parse() # initialize shell shell = ShellParser() opts, args = shell.get_args() Log.debug("Running '{o}' with arguments: {a}", o=opts, a=args) # return shell arguments return opts, vars(args)
def call(self): """Call dispatcher. Handlers option if is identified and supported. Raises: Exception: If unsupported option is provided. """ if self.option not in self.supported_options: Log.fatal("Unsupported option {o}", o=self.option) access = self.supported_options[self.option] debug_message = "Calling option {opt} with access level {acc}" Log.debug(debug_message, opt=self.option, acc=access) method = "call_{}_{}_option".format(access, self.option) if not hasattr(self, method): error = "Unsupported method {option} or access rights {access}" Log.fatal(error, option=self.option, access=access) return getattr(self, method)(**self.args)
def call_debug_write_purge_option(self, keys): """Permanently remove keys from keychain in debug mode. Can corrupt keychain! (Very unsafe) """ Log.debug("Incoming debug purge request...") for key in keys: Log.debug("Attempt to remove {k} ...", k=key) removed_key = self.get_secrets().remove(key) if removed_key is not None: Log.debug("Removed {k} ...", k=key) Log.debug("Closing...")
def call_read_only_list_option(self, *args, **kwargs): """List handler. Outputs: stdout: Pager with table-like view of all hostnames. """ Log.debug("Incoming list request...") known_hosts = [e for e in self.get_db().query_all()] counter = -1 indexes = {} sorted_hosts = [] Log.debug("Found {n} hosts to list...", n=len(known_hosts)) while len(known_hosts) > 0: if counter >= self.MAX_ITER_LIST: Log.fatal("Max. iteration over list display reached...") name, auth, host, jump = known_hosts[0] counter += 1 if auth.signature() not in indexes: indexes.update({auth.signature(): counter}) if jump is None: sorted_hosts.append(known_hosts.pop(0)) continue jump_index = indexes.get(jump.signature(), -1) if jump_index > -1: if jump_index == 0: jump_index += 1 sorted_hosts.insert(jump_index, known_hosts.pop(0)) continue known_hosts.insert(len(known_hosts), known_hosts.pop(0)) Log.debug("Sorted all known hosts and now preparing to print out...") Display.show_list_view(sorted_hosts, **self.args)
def deploy_script(script_name): """Install script on current machine. Args: script_name (str): The name of script to be deployed. Raises: Exception: If script is missing or cannot be accessed. Return: bool: True if successfully deployed, otherwise False. """ content = read_helper_script("data/shell/{}".format(script_name)) if not content: Log.fatal("Helper script is missing") script = "{}\n{}".format(SCRIPTS_SHEBANG, content) try: script_name, _ = script_name.split(".", 1) except Exception: Log.debug("Cannot find file extension for {s}", s=script_name) return make_helper_script(script_name, script)
def call_debug_read_dump_option(self, keys): """Dump all keys from keychain in debug mode. """ Log.debug("Incoming debug dump request...") for key in keys: for k in self.get_secrets().lookup(key): key_type = self.get_db().which(k) Log.debug(" [{t}] {k}", k=self.get_db().shift(k), t=key_type) Log.debug("Closing...")
def make_helper_script(file_script, script_content): """Create executable helper script. Args: file_script (str): Script to make. script_content (unicode): Script content. Raises: Exception: If application cannot create directory or file. Return: bool: True if script was successfully created, otherwise False. """ if is_root(): scripts_dir = SYSTEM_SCRIPTS_DIR else: scripts_dir = path.join(expanduser("~"), SCRIPTS_DIR) Log.debug("Scripts directory located at {path}", path=scripts_dir) try: if not path.exists(scripts_dir): Log.debug("Scripts directory does not exist. Creating...") makedirs(scripts_dir) Log.debug("Scripts directory successfully created!") else: Log.debug("Found scripts directory...") except Exception as e: Log.fatal("Failed to create scripts directory: {err}", err=str(e)) try: filepath = "{}/{}".format(scripts_dir, file_script) if path.exists(filepath): Log.fatal("A file with this name already exists: {x}", x=filepath) with open(filepath, "wb") as fd: fd.write(script_content) mod = stat(filepath) chmod(filepath, mod.st_mode | S_IEXEC) return True except Exception as e: Log.fatal("Cannot create helper script because: {e}", e=str(e)) return False
def get_secret_dir(cls): """Used to store keys credentials and configuration files. Raises: Exception: If application cannot create directory. Return: unicode: Application secret directory. """ secret_dir = path.join(expanduser("~"), cls.UNLOCKER_DIR) Log.debug("Secret directory located at {path}", path=secret_dir) try: if not path.exists(secret_dir): Log.debug("Secret directory does not exist. Creating...") makedirs(secret_dir) Log.debug("Secret directory successfully created!") else: Log.debug("Found secret directory...") except Exception as e: Log.fatal("Failed to create secret directory: {err}", err=str(e)) return unicode(secret_dir)
def call_read_write_append_option(self, name, host, port, user, auth, scheme, jump_server, **kwargs): """Append secrets to a named authority. Args: name (str): The name of the authority to lookup. host (str): Hostname to attach to authority. port (int): Port number to attach to authority. user (str): Username to attach to authority. auth (str): Authentification method. scheme (str): Scheme of the connection. jump_server (str): Name or signature of another authority. Raises: Exception: If named authority already exists. Outputs: stdout: Confirm message with human-readable authority details. """ Log.debug("Incoming append request...") if name is None: name = self.build_random_name() Log.warn("Named authority is known as: {n}", n=name) elif len(name) < self.MIN_NAME_LEN: error = "Possible lookup collision: entry name is too short " \ "(name must be at least {size} characters)" Log.fatal(error, size=self.MIN_NAME_LEN) elif len(name) > self.MAX_NAME_LEN: error = "Possible bad naming: entry name is too long " \ "(name must be at most {size} characters)" Log.fatal(error, size=self.MAX_NAME_LEN) elif self.get_db().exists(name): error = "Another entry with the same name exists: {name} " \ "(name must be unique)" Log.fatal(error, name=name) data = { "name": name, "host": host, "auth": self.build_authority_from_args(user, host, port, scheme), } if jump_server is not None: data.update({ "jump_auth": self.build_authority_from_signature(jump_server) }) Log.debug("Preparing to add {args}", args=data) self.get_db().add(passkey=Passkey.resolve(auth), **data) Log.debug("New named authority is saved...") Display.show_append(data.get("auth"))
def call_read_only_lookup_option(self, name, **kwargs): """Lookup secrets for a named authority. Args: name (str): The name of the authority to lookup. Raises: Exception: If named authority does not exists. Outputs: stdout: Human-readable passkey with authority details and hostname. """ Log.debug("Incoming lookup request for named authority {n}", n=name) if not self.get_db().exists(name): Log.debug("Nothing to lookup...") error = "Cannot lookup entry: \"{name}\" not found in " \ "keychain (missing key)" Log.fatal(error, name=name) Log.debug("Fetching data for named authority...") auth, host, secret = self.get_db().lookup(name) passtype, passkey = Passkey.copy(secret, True) Log.debug("Printing as safe as possible {n}'s secrets...", n=name) Display.show_lookup(auth, host, passtype, passkey)
def __init__(self, holder): self.keychain = holder Log.debug("Keychain initialized...")
def __init__(self, storage): if not isinstance(storage, Keychain): Log.fatal("Unexpected database storage {t}", t=type(storage)) self.storage = storage Log.debug("Database initialized...") Log.debug("Storage status: {k}", k=str(storage))
def call_read_write_remove_option(self, name, **kwargs): """Lookup and remove secrets for a named authority. Args: name (str): The name of the authority to lookup. Raises: Exception: If named authority does not exists. Outputs: stdout: Human-readable passkey with authority details and hostname. """ Log.debug("Incoming remove request for named authority {n}", n=name) if not self.get_db().exists(name): Log.debug("Nothing to remove...") error = "Cannot remove entry: \"{name}\" not found in " \ "keychain (missing key)" Log.fatal(error, name=name) Log.debug("Fetching data for named authority...") auth, host, secret = self.get_db().lookup(name) dependents = [] for jump, dep_name in self.get_db().query_jump(): if jump.signature() == auth.signature(): debug_message = "Found another authority {a} " \ "depending on this... " Log.debug(debug_message, a=str(jump)) dependents.append(dep_name) if len(dependents) > 0: error = "Not removing entry because {n} other servers bounce of " \ "\"{name}\": remove all before trying again (safe mode)" Log.fatal(error, n=len(dependents), name=name) passtype, passkey = Passkey.copy(secret, True) Log.debug("Removing named authority...") self.get_db().remove(name) # remove all keys for named auth Log.debug("Permanently removed named authority {n}", n=name) Display.show_remove(auth, host, passtype, passkey)