def new(cls, host, port, user, scheme=None): """Create new authority instance. Args: host (str): Hostname or IP address as an integer. port (int): Port number of hostname. user (str): Username assigned to hostname. scheme (str): Connection service scheme (optional). Raises: Exception: If required fields are invalid. Returns: Authority: An authority instance. """ auth = cls() try: if scheme is not None: auth.set_scheme(scheme) auth.set_host(host) auth.set_port(port) auth.set_user(user) except Exception as e: Log.fatal("Cannot create new authority: {e}", e=str(e)) return auth
def read_tmp_secrets(self, zf_secrets): """Read temporary secrets. Args: zf_secrets (ZipFile): ZipFile instance with secrets and passkeys. Raises: SystemExit: If manager crashes or corrupted data are found. """ for each in zf_secrets.read(self.migrate_tmpfile).split("\n"): line = each.split("\t", self.COLUMNS) if len(line) == 0: continue _, _, host, ipv4, port, user, scheme, name, ptype, passkey = line if ptype not in Passkey.SUPPORTED_TYPES: Log.fatal("Unsupported passkey storage {x}", x=ptype) authority_args = user, host, port, scheme auth = self.manager.build_authority_from_args(*authority_args) if ptype == "privatekey": passkey = zf_secrets.read(passkey) data = { "name": name, "host": host, "auth": auth, "passkey": Passkey.SUPPORTED_TYPES.get(ptype) + passkey } self.manager.get_db().add(**data) Log.warn("Unsupported import for jump server, yet")
def get_scheme(self): """Authority scheme getter. Returns: unicode: Connection scheme. """ if self.scheme is None: Log.fatal("Authority has not set a valid scheme: unset scheme") return unicode(self.scheme)
def get_user(self): """Authority user getter. Returns: unicode: Connection username. """ if self.user is None: Log.fatal("Authority has not set a valid user: unset user") return unicode(self.user)
def get_init_shell(self): """Shell getter for "init" option. """ try: Secret.get_secret_file() print(OKAY_MESSAGE) except Exception as e: Log.fatal("Aborting due to an error: {e}", e=str(e)) raise SystemExit
def read(self): """Autodetect read method and retrieve passkey. Raises: Exception: If cannot autodetect read method. """ if not hasattr(self, self.__read_method): Log.fatal("Unsupported read method: {m}", m=self.read_method) self.passkey = getattr(self, self.__read_method)()
def get_db(self): """Database getter. Returns: Database: Database storage. """ if isinstance(self.__database, Database): return self.__database Log.fatal("Missing database: manager not initialized?")
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 get_secrets(self): """Keychain getter. Returns: Keychain: Passkeys storage. """ if isinstance(self.__secrets, Keychain): return self.__secrets Log.fatal("Missing keychain: manager not initialized?")
def get_install_shell(self): """Shell getter for "install" option. """ try: deploy_unlock_script() and deploy_lock_script() print(SCRIPTS_CREATED) except Exception as e: Log.fatal("Aborting due to an error: {e}", e=str(e)) raise SystemExit
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 add(self, key, value): """Append key to keychain. Args: key (str): Key to append. value (str): Value to save for given key. """ if self.has(key): Log.fatal("Cannot add duplicates in keychain") self.update(key, value)
def pin(self): """Return the prefixed secret with its appropriate type. Raises: Exception: If unsupported passkey is provided. Returns: str: Prefixed passkey. """ if self.passkey is None: Log.fatal("Passkey has not been read yet") return self.passfix + self.passkey
def sign(cls, data): """Calculate CRC32 hash of given data. Args: data (str): String data to calculate CRC. Returns: str: Hex value without 0x of the calculated CRC32. """ if not isinstance(data, (str, unicode)): Log.fatal("Cannot calculate CRC for non-string data") return hex(crc32(data) & 0xFFFFFFFF)[2:] # skip 0x
def get_secret_file(cls): """Return or make a sample keys holder. Raises: Exception: If application cannot read or write file. Returns: object: Instance of opened secrets file. """ secret_dir = cls.get_secret_dir() lockpath = "{}/{}".format(secret_dir, cls.SECRETS_LOCK) if path.exists(lockpath): Log.fatal("Secrets are locked!\nClosing...") fullpath = "{}/{}".format(secret_dir, cls.SECRETS_FILE) try: secret_file = read_secrets(fullpath, "c") chmod(fullpath, 0600) except Exception as e: Log.fatal("Cannot create secret storage file: {e}", e=str(e)) try: assert secret_file[cls.VERSION] except KeyError: secret_file[cls.VERSION] = __version__ except Exception as e: Log.fatal("Unsupported secrets driver or {e}", e=str(e)) if secret_file[cls.VERSION] != __version__: error = "Secrets have been stored with a different version " \ "of Unlocker (current version {cv}; secrets {vs})\n" \ "Closing..." Log.fatal(error, cv=__version__, vs=secret_file[cls.VERSION]) return secret_file
def add_host(self, name, host): """Create new hostname for named authority. Args: name (str): Full name of the authority to add. host (str): Hostname to save to keychain. Raises: Exception: If named authority already exists in keychain. """ if self.storage.has(self.get_host_key(name)): Log.fatal("Cannot add hostname on a duplicate entry") self.storage.add(self.get_host_key(name), host)
def update_jump_auth(self, name, auth): """Update passkey for existing named authority. Args: name (str): Full name of the authority to add. auth (Authority): New authority as jump server. Raises: Exception: If provided argument is not Authority. """ if not isinstance(auth, Authority): Log.fatal("Expected authority instance, got {t}", t=type(auth)) self.storage.update(self.get_jump_key(name), auth.read())
def update_passkey(self, name, passkey): """Update passkey for existing named authority. Args: name (str): Full name of the authority to add. passkey (str): New processed passkey to replace the old passkey. Raises: Exception: If passkey is zero-length. """ if len(passkey) == 0: Log.fatal("Passkey cannot be empty") self.storage.update(self.get_pass_key(name), passkey)
def migrate_secrets(cls): """Migrate stored secrets from another version to current version. Raises: Exception: If secrets cannot be migrated. """ fullpath = "{}/{}".format(cls.get_secret_dir(), cls.SECRETS_FILE) try: secret_file = read_secrets(fullpath, "c") secret_file[cls.VERSION] = __version__ secret_file.close() except Exception as e: Log.fatal("Cannot migrate secrets because {e}", e=str(e))
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 add_passkey(self, name, passkey): """Create new passkey for a named authority. Args: name (str): Full name of the authority to add. passkey (str): Processed passkey to save to keychain. Raises: Exception: If named authority already exists in keychain. """ if self.exists(name): Log.fatal("Cannot add passkey on a duplicate entry") self.update_passkey(name, 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 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 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 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 remove(self, key): """Remove key from keychain. Args: key (str): Key to remove. Returns: str: Raw "as is" removed value for given key. """ value = self.get(key) if value is None: Log.warn("Keychain can not remove an unset key") else: del self.keychain[key] return value
def query(self, key_type_prefix): """Query secrets from keychain storage. Args: key_type_prefix (str): Prefix to partial match in lookup. Yields: mixt: Entry for each matched found. """ for each in self.storage.lookup(key_type_prefix): if each == self.VERSION: continue if self.storage.get_value(each).strip() == "": Log.fatal("Storage contains empty value for key {k}", k=each) yield each
def shift(self, string): """Shift to the right a sting to remove any prefix. Args: string (str): String to be shifted. Raises: Exeption: If provided argument is not a string. Returns: str: New shifted string without prefix. """ if isinstance(string, (str, unicode)): return string[self.PREFIX_FIXED_LEN:] Log.fatal("Unsupported shift operation on {t}", t=type(string))
def add_auth(self, name, auth): """Create new authority for self named authority. Args: name (str): Full name of the authority to add. auth (Authority): Authority instance to save to keychain. Raises: Exception: If named authority already exists in keychain. """ if self.storage.has(self.get_auth_key(name)): Log.fatal("Cannot add authority on a duplicate entry") if not isinstance(auth, Authority): Log.fatal("Expected auth to be authority, got {t}", t=type(auth)) self.storage.add(self.get_auth_key(name), auth.read())
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)