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 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 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 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 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 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_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 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_db(self): """Database getter. Returns: Database: Database storage. """ if isinstance(self.__database, Database): return self.__database Log.fatal("Missing database: 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 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 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 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 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 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_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 set_scheme(self, scheme): """Authority scheme setter. Args: scheme (str): Connection service scheme. Raises: Exception: if an invalid scheme is provided. """ if not isinstance(scheme, (str, unicode)): Log.fatal("Invalid scheme: expected string, got {x}", x=type(scheme)) if len(scheme) == 0: Log.fatal("Invalid scheme: zero-length string not allowed") self.scheme = scheme
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 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 get_prefix(self, prefix): """Prefix getter. Appends a separator and returns prefix. Args: prefix (str): The prefix to return. Raises: Exception: If size of prefix exceeds. Returns: str: New string of prefix with separator. """ key = prefix + self.SEPARATOR if len(key) != self.PREFIX_FIXED_LEN: Log.fatal("Invalid prefixed key length, got {v}", v=len(key)) return key
def set_port(self, port): """Authority port setter. Args: port (int): Port number of connection. Raises: Exception: if an invalid port is provided. """ if isinstance(port, (str, unicode)): port = int(port) if not isinstance(port, int): Log.fatal("Invalid port: expected integer, got {x}", x=type(port)) if port < self.MIN_PORT or port > self.MAX_PORT: Log.fatal("Invalid port: out of range {port}", port=port) self.port = port
def build_authority_from_signature(self, signature): """Generate authority from a signature. Args: signature (str): The signature to generate the authority. Raises: Exception: If no authority matches the signature provided. Returns: Authority: The authority with the signature provided. """ for each, _ in self.get_db().query_auth(): if each.signature() == signature: return each Log.fatal("Cannot find authority for signature {s}", s=signature)
def get_value(self, key): """Returns real value for given key. Args: key (str): Key to lookup and retrieve value. Raises: Exception: If value is None or keychain does not have key. Returns: str: Uncompressed and decoded stored value for key. """ value = self.get(key) if value is None: Log.fatal("Keychain does not have requested key") return decompress(b64decode(value))
def find_port(cls, service): """Find port number for service. Args: service (str): Service scheme name. Raises: Exception: If service is not supported. Return: int: Port number for service. """ for srv, port in cls.services: if srv == service: return port Log.fatal("Unsupported service {s}", s=service)
def lookup(self, lookup_name): """Lookup a named authority and return self, hostname and secret. Args: lookup_name (str): The name of the authority to find. Raises: Exception: If authority is not found. Returns: tuple: Authority instance, hostname and secret passkey. """ for name, auth, host, jump in self.query_all(): if name == lookup_name: secret = self.storage.get_value(self.get_pass_key(name)) return auth, host, secret Log.fatal("Nothing found for name {n}", n=lookup_name)