def fmt_field(name, value='', key=None, level=0): hl = False # resolve values if isinstance(value, Script): hl = True value = value.script elif cls.is_secret(name, key): reveal = "reveal with: {}".format( HighlightColor( join('avendesora', 'value', cls.get_name(), cls.combine_field(name, key)))) value = ', '.join(cull([value.get_description(), reveal])) elif isinstance(value, (GeneratedSecret, ObscuredSecret)): v = cls.get_scalar(name, key) value = ', '.join(cull([value.get_description(), str(v)])) else: value = str(value) # format values if '\n' in value: value = indent(dedent(value), get_setting('indent')).strip('\n') sep = '\n' elif value: sep = ' ' else: sep = '' if hl: value = HighlightColor(value) name = str(name).replace('_', ' ') leader = level * get_setting('indent') return indent( LabelColor((name if key is None else str(key)) + ':') + sep + value, leader)
def conceal(cls, plaintext, decorate=False, encoding=None, symmetric=False, gpg_ids=None): encoding = encoding if encoding else get_setting('encoding') plaintext = str(plaintext).encode(encoding) if not gpg_ids: gpg_ids = get_setting('gpg_ids', []) if is_str(gpg_ids): gpg_ids = gpg_ids.split() encrypted = cls.gpg.encrypt(plaintext, gpg_ids, armor=True, symmetric=bool(symmetric)) if not encrypted.ok: msg = ' '.join( cull( ['unable to encrypt.', getattr(encrypted, 'stderr', None)])) raise PasswordError(msg) ciphertext = str(encrypted) if decorate: return 'GPG("""\n%s""")' % indent(ciphertext) else: return ciphertext
def main(): with Inform() as inform: # read command line cmdline = docopt( __doc__.format(commands=Command.summarize()), options_first=True ) config = cmdline['--config'] command = cmdline['<command>'] args = cmdline['<args>'] options = cull([ 'verbose' if cmdline['--verbose'] else '', 'narrate' if cmdline['--narrate'] else '', 'trial-run' if cmdline['--trial-run'] else '', ]) if cmdline['--narrate']: inform.narrate = True try: cmd, name = Command.find(command) with Settings(config, cmd.REQUIRES_EXCLUSIVITY) as settings: cmd.execute(name, args, settings, options) except KeyboardInterrupt: display('Terminated by user.') except Error as err: err.terminate() except OSError as err: fatal(os_error(err)) terminate()
def save(self, contents, gpg_ids=None): path = self.path if not gpg_ids: gpg_ids = get_setting('gpg_ids', []) if is_str(gpg_ids): gpg_ids = gpg_ids.split() if not gpg_ids: raise Error('must specify GPG ID.') use_gpg, use_armor = self._choices() if use_gpg: try: encoded = contents.encode(get_setting('encoding')) encrypted = self.gpg.encrypt(encoded, gpg_ids, armor=use_armor) if not encrypted.ok: msg = ' '.join(cull([ 'unable to encrypt.', getattr(encrypted, 'stderr', None) ])) raise Error(msg, culprit=path, sep='\n') else: if use_armor: path.write_text(str(encrypted)) else: path.write_bytes(encrypted.data) except ValueError as err: raise Error(str(err), culprit=path) else: path.write_text(contents, get_setting('encoding')) path.chmod(0o600)
def main(): with Inform(error_status=2, flush=True, version=version) as inform: # read command line cmdline = docopt(expanded_synopsis, options_first=True, version=version) config = cmdline["--config"] command = cmdline["<command>"] args = cmdline["<args>"] if cmdline["--mute"]: inform.mute = True if cmdline["--quiet"]: inform.quiet = True emborg_opts = cull( [ "verbose" if cmdline["--verbose"] else "", "narrate" if cmdline["--narrate"] else "", "dry-run" if cmdline["--dry-run"] else "", "no-log" if cmdline["--no-log"] else "", ] ) if cmdline["--narrate"]: inform.narrate = True try: # find the command cmd, cmd_name = Command.find(command) # execute the command initialization exit_status = cmd.execute_early(cmd_name, args, None, emborg_opts) if exit_status is not None: terminate(exit_status) worst_exit_status = 0 try: while True: with Settings(config, cmd, emborg_opts) as settings: try: exit_status = cmd.execute( cmd_name, args, settings, emborg_opts ) except Error as e: settings.fail(e, cmd=' '.join(sys.argv)) e.terminate() if exit_status and exit_status > worst_exit_status: worst_exit_status = exit_status except NoMoreConfigs: pass # execute the command termination exit_status = cmd.execute_late(cmd_name, args, None, emborg_opts) if exit_status and exit_status > worst_exit_status: worst_exit_status = exit_status except KeyboardInterrupt: display("Terminated by user.") except Error as e: e.terminate() except OSError as e: fatal(os_error(e)) terminate(worst_exit_status)
def __init__(self, value, is_secret, name=None, key=None, desc=None): self.value = value self.is_secret = is_secret self.name = name self.key = str(key) if key is not None else key self.field = '.'.join(cull([name, self.key])) self.desc = desc
def converter(cls, to, data): # cannot convert this to '$' directly using cryptocompare. # instead, use EOS as intermediary conversion = data[cls.UNITS]['EOS'] * data['EOS'][to[-1]] units = getattr(cls, 'UNITS', None) symbol = getattr(cls, 'SYMBOL', None) return UnitConversion(to, cull([symbol, units]), conversion)
def _inform_get_kwargs(self): kwargs = {} if hasattr(self, 'script'): kwargs['script'] = getattr(self, 'script') if kwargs['script'] is True: del kwargs['script'] # don't clutter arg list with a default if hasattr(self, 'name'): kwargs['name'] = getattr(self, 'name') return cull(kwargs)
def edit(self): self.read_defaults() cmd = ( self.cmd.split() + ['-S', settings] + cull([self.file1, self.file2, self.file3, self.file4]) ) self.vim = Cmd(cmd, modes='W1') return self.vim.run()
def main(): with Inform(error_status=2, flush=True, version=version) as inform: # read command line cmdline = docopt(expanded_synopsis, options_first=True, version=version) config = cmdline['--config'] command = cmdline['<command>'] args = cmdline['<args>'] if cmdline['--mute']: inform.mute = True if cmdline['--quiet']: inform.quiet = True options = cull([ 'verbose' if cmdline['--verbose'] else '', 'narrate' if cmdline['--narrate'] else '', 'trial-run' if cmdline['--trial-run'] else '', 'no-log' if cmdline['--no-log'] else '', ]) if cmdline['--narrate']: inform.narrate = True try: # find the command cmd, cmd_name = Command.find(command) # execute the command initialization exit_status = cmd.execute_early(cmd_name, args, None, options) if exit_status is not None: terminate(exit_status) worst_exit_status = 0 try: while True: with Settings(config, cmd, options) as settings: try: exit_status = cmd.execute(cmd_name, args, settings, options) except Error as e: settings.fail(e) e.terminate() if exit_status and exit_status > worst_exit_status: worst_exit_status = exit_status except NoMoreConfigs: pass # execute the command termination exit_status = cmd.execute_late(cmd_name, args, None, options) if exit_status and exit_status > worst_exit_status: worst_exit_status = exit_status except KeyboardInterrupt: display('Terminated by user.') except Error as e: e.terminate() except OSError as e: fatal(os_error(e)) terminate(worst_exit_status)
def gather_public_keys(self): comment(' gathering public keys') keyname = self.keyname data = self.data clients = conjoin(self.data.get('clients', [])) default_purpose = fmt('This key allows access from {clients}.') purpose = self.data.get('purpose', default_purpose) servers = self.data.get('servers', []) prov = '.provisional' if self.trial_run else '' # read contents of public key try: pubkey = to_path(keyname + '.pub') key = pubkey.read_text().strip() except OSError as err: narrate('%s, skipping.' % os_error(err)) return # get fingerprint of public key try: keygen = Run(['ssh-keygen', '-l', '-f', pubkey], modes='wOeW') fields = keygen.stdout.strip().split() fingerprint = ' '.join([fields[0], fields[1], fields[-1]]) except OSError as err: error(os_error(err)) return # contribute commented and restricted public key to the authorized_key # file for each server for server in servers: if self.update and server not in self.update: continue if server in self.skip: continue server_data = servers[server] description = server_data.get('description', None) restrictions = server_data.get('restrictions', []) remarks = [ '# %s' % t for t in cull([purpose, description, self.warning, fingerprint]) if t ] include_file = server_data.get( 'remote-include-filename', data['remote-include-filename'] ) bypass = server_data.get('bypass') authkeys = AuthKeys(server, include_file, bypass, self.trial_run) authkeys.add_public_key(keyname, key, remarks, restrictions) if not servers: warn( 'no servers specified, you must update them manually.', culprit=keyname )
def cleanup(self): if self.vim: self.vim.kill() for each in cull([self.file1, self.file2, self.file3, self.file4]): path = to_path(each) dn = path.parent fn = path.name swpfile = to_path(dn, '.' + fn + '.swp') try: rm(swpfile) except OSError as e: error(os_error(e))
def duplicity_options(settings, options): args = [] gpg_binary = settings.value('gpg_binary') if gpg_binary: args.extend(['--gpg-binary', str(to_path(gpg_binary))]) if DUPLICITY_LOG_FILE: args.extend(f'--log-file {DUPLICITY_LOG_FILE}'.split()) rm(DUPLICITY_LOG_FILE) if settings.ssh_backend_method == 'option': args.extend('--ssh-backend pexpect'.split()) args.append('-v9' if 'verbose' in options else '-v8') args.append('--dry-run' if 'trial-run' in options else '') return cull(args)
def render_script(account, field): # if field was not given if not field: name, key = account.split_field(field) field = '.'.join(cull([name, key])) # treat field as name rather than script if it there are not attributes if '{' not in field: name, key = account.split_field(field) value = account.get_scalar(name, key) is_secret = account.is_secret(name, key) label = account.combine_field(name, key) try: alt_name = value.get_key() if alt_name: label += ' (%s)' % alt_name except AttributeError: pass return dedent(str(value)).strip(), is_secret, label script = field # Run the script regex = re.compile(r'({[\w. ]+})') out = [] is_secret = False for term in regex.split(script): if term and term[0] == '{' and term[-1] == '}': # we have found a command cmd = term[1:-1].lower().strip() if cmd == 'tab': out.append('\t') elif cmd == 'return': out.append('\n') elif cmd.startswith('sleep '): pass elif cmd.startswith('rate '): pass elif cmd.startswith('remind '): notify(term[8:-1].strip()) else: if cmd.startswith('paste '): cmd = cmd[6:] name, key = account.split_field(cmd.strip()) value = account.get_scalar(name, key) out.append(dedent(str(value)).strip()) if account.is_secret(name, key): is_secret = True else: out.append(term) return ''.join(out), is_secret, None
def publish(self): narrate('publishing authorized_keys to', self.server) prov = '.provisional' if self.trial_run else '' entries = [ fmt("# This file was generated by sshdeploy on {date}.") ] if self.include: entries += [ '\n'.join([ fmt('# Contents of {self.include_file}:'), self.include ]) ] for name in sorted(self.keys.keys()): key = self.keys[name] comment = self.comment[name] comment = [comment] if is_str(comment) else comment restrictions = self.restrictions[name] if not is_str(restrictions): restrictions = ','.join(restrictions) restricted_key = ' '.join(cull([restrictions, key])) entries.append('\n'.join(comment + [restricted_key])) # delete any pre-existing provisional files # the goal here is to leave a clean directory when not trial-run try: run_sftp(self.server, [ fmt('rm .ssh/authorized_keys.provisional') ]) except OSError as err: pass # now upload the new authorized_keys file try: authkey = to_path('authorized_keys.%s' % self.server) with authkey.open('w') as f: f.write('\n\n'.join(entries) + '\n') authkey.chmod(0o600) if self.bypass: warn( 'You must manually upload', fmt('<keydir>/authorized_keys.{self.server}.'), culprit=self.server ) else: run_sftp(self.server, [ fmt('put -p {authkey} .ssh/authorized_keys{prov}') ]) except OSError as err: error(os_error(err))
def read(self): path = self.path # file is only assumed to be encrypted if path has gpg extension if path.suffix.lower() in GPG_EXTENSIONS: with path.open('rb') as f: try: decrypted = self.gpg.decrypt_file(f) if not decrypted.ok: msg = ' '.join(cull([ 'unable to decrypt.', getattr(decrypted, 'stderr', None) ])) raise Error(msg, culprit=path, sep='\n') except ValueError as err: raise Error(str(err), culprit=path) return decrypted.data.decode(get_setting('encoding')) else: return path.read_text(encoding=get_setting('encoding'))
def read(self): path = self.path # file is only assumed to be encrypted if path has gpg extension if path.suffix.lower() in GPG_EXTENSIONS: try: with path.open('rb') as f: decrypted = self.gpg.decrypt_file(f) if not decrypted.ok: msg = ' '.join( cull([ 'unable to decrypt.', getattr(decrypted, 'stderr', None) ])) raise PasswordError(msg, culprit=path, sep='\n') except ValueError as e: raise PasswordError(full_stop(e), culprit=path) except OSError as e: raise PasswordError(os_error(e)) return decrypted.data.decode(get_setting('encoding')) else: return path.read_text(encoding=get_setting('encoding'))
def save(self, contents, gpg_ids=None): path = self.path if not gpg_ids: gpg_ids = get_setting('gpg_ids', []) if is_str(gpg_ids): gpg_ids = gpg_ids.split() if not gpg_ids: # raise PasswordError('must specify GPG ID.') log('no gpg id available, using symmetric encryption.') use_gpg, use_armor = self._choices() if use_gpg: try: encoded = contents.encode(get_setting('encoding')) if gpg_ids: encrypted = self.gpg.encrypt(encoded, gpg_ids, armor=use_armor) else: encrypted = self.gpg.encrypt(encoded, None, symmetric='AES256', armor=use_armor) if not encrypted.ok: msg = ' '.join( cull([ 'unable to encrypt.', getattr(encrypted, 'stderr', None) ])) raise PasswordError(msg, culprit=path, sep='\n') else: path.write_bytes(encrypted.data) except ValueError as e: raise PasswordError(full_stop(e), culprit=path) else: path.write_text(contents, encoding=get_setting('encoding')) self.chmod()
def open_browser(cls, name, key=None): browser = cls.get_field("browser", default=None) if browser is None or is_str(browser): browser = StandardBrowser(name) # get the urls from the urls attribute if not key: key = getattr(cls, "default_url", None) urls = getattr(cls, "urls", []) if type(urls) != dict: if is_str(urls): urls = urls.split() urls = {None: urls} # get the urls from the url recognizers # currently urls from recognizers dominate over those from attributes discovery = getattr(cls, "discovery", ()) for each in Collection(discovery): urls.update(each.all_urls()) # select the urls try: urls = urls[key] except TypeError: if key: raise Error("keys are not supported with urls on this account.", culprit=key) except KeyError: keys = cull(urls.keys()) if keys: raise Error("unknown key, choose from %s." % conjoin(keys), culprit=key) else: raise Error("keys are not supported with urls on this account.", culprit=key) url = list(Collection(urls))[0] # use the first url specified # open the url browser.run(url)
def main(): with Inform(error_status=2, flush=True, version=version) as inform: # read command line cmdline = docopt(expanded_synopsis, options_first=True, version=version) config = cmdline['--config'] command = cmdline['<command>'] args = cmdline['<args>'] if cmdline['--mute']: inform.mute = True options = cull([ 'verbose' if cmdline['--verbose'] else '', 'narrate' if cmdline['--narrate'] else '', 'trial-run' if cmdline['--trial-run'] else '', 'no-log' if cmdline['--no-log'] else '', ]) if cmdline['--narrate']: inform.narrate = True try: cmd, cmd_name = Command.find(command) with Settings(config, cmd.REQUIRES_EXCLUSIVITY, options) as settings: try: exit_status = cmd.execute(cmd_name, args, settings, options) except Error as e: settings.fail(e) e.terminate(True) except KeyboardInterrupt: display('Terminated by user.') exit_status = 0 except Error as e: e.terminate() except OSError as e: fatal(os_error(e)) terminate(exit_status)
def run(cls, command, args, settings, options): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) # check for required settings src_dirs = render_paths(settings.src_dirs) if not src_dirs: raise Error('src_dirs: setting has no value.') # check the dependencies are available for each in settings.values('must_exist'): path = to_path(each) if not path.exists(): raise Error('does not exist, perform setup and restart.', culprit=each) # run prerequisites cmds = settings.value('run_before_backup') if is_str(cmds): cmds = [cmds] for cmd in cull(cmds): narrate('running pre-backup script:', cmd) try: Run(cmd, 'SoEW') except Error as e: e.reraise(culprit=('run_before_backup', cmd.split()[0])) # run borg try: settings.run_borg( cmd='create', args=[settings.destination(True)] + render_paths(settings.src_dirs), emborg_opts=options, ) except Error as e: if e.stderr and 'is not a valid repository' in e.stderr: e.reraise( codicil="Run 'emborg init' to initialize the repository.") else: raise # update the date files narrate('update date file') now = arrow.now() settings.date_file.write_text(str(now)) # run any scripts specified to be run after a backup cmds = settings.value('run_after_backup') if is_str(cmds): cmds = [cmds] for cmd in cull(cmds): narrate('running post-backup script:', cmd) try: Run(cmd, 'SoEW') except Error as e: e.reraise(culprit=('run_after_backup', cmd.split()[0])) if cmdline['--fast']: return # prune the archives if requested try: # check the archives if requested activity = 'checking' if settings.check_after_create: narrate('checking archive') check = CheckCommand() check.run('check', [], settings, options) activity = 'pruning' if settings.prune_after_create: narrate('pruning archives') prune = PruneCommand() prune.run('prune', [], settings, options) except Error as e: e.reraise( codicil=(f'This error occurred while {activity} the archives.', 'No error was reported while creating the archive.'))
def open_browser(cls, key=None, browser_name=None, list_urls=False): if not browser_name: browser_name = cls.get_scalar('browser', default=None) browser = StandardBrowser(browser_name) # get the urls from the urls attribute # this must be second so it overrides those from recognizers. primary_urls = getattr(cls, 'urls', []) if type(primary_urls) != dict: if is_str(primary_urls): primary_urls = primary_urls.split() primary_urls = {None: primary_urls} if primary_urls else {} # get the urls from the url recognizers discovery = getattr(cls, 'discovery', ()) urls = {} for each in Collection(discovery): urls.update(each.all_urls()) # combine, primary_urls must be added to urls, so they dominate urls.update(primary_urls) if list_urls: default = getattr(cls, 'default_url', None) for name, url in urls.items(): if is_collection(url): url = list(Collection(url))[0] if name == default: url += HighlightColor(' [default]') if not name: name = '' elif not name: continue output(LabelColor('{:>24s}:'.format(name)), url) return # select the urls keys = cull(list(urls.keys())) if not key: key = getattr(cls, 'default_url', None) if not key and keys and len(keys) == 1: key = keys[0] try: urls = urls[key] except KeyError: if keys: if key: msg = 'unknown key, choose from {}.' else: msg = 'key required, choose from {}.' raise PasswordError(msg.format(conjoin(repr(k) for k in keys)), culprit=key) else: if key: raise PasswordError( 'keys are not supported with urls on this account.', culprit=key) else: raise PasswordError('no url available.') # open the url urls = Collection(urls) url = list(urls)[0] # use the first url specified browser.run(url)
def main(): with Inform( error_status=2, flush=True, logfile=LoggingCache(), prog_name='emborg', version=version, ) as inform: # read command line cmdline = docopt(expanded_synopsis, options_first=True, version=version) config = cmdline["--config"] command = cmdline["<command>"] args = cmdline["<args>"] if cmdline["--mute"]: inform.mute = True if cmdline["--quiet"]: inform.quiet = True if cmdline["--relocated"]: os.environ['BORG_RELOCATED_REPO_ACCESS_IS_OK'] = 'YES' emborg_opts = cull([ "verbose" if cmdline["--verbose"] else "", "narrate" if cmdline["--narrate"] else "", "dry-run" if cmdline["--dry-run"] else "", "no-log" if cmdline["--no-log"] else "", ]) if cmdline["--narrate"]: inform.narrate = True Hooks.provision_hooks() worst_exit_status = 0 try: # find the command cmd, cmd_name = Command.find(command) # execute the command initialization exit_status = cmd.execute_early(cmd_name, args, None, emborg_opts) if exit_status is not None: terminate(exit_status) queue = ConfigQueue(cmd) while queue: with Settings(config, emborg_opts, queue) as settings: try: exit_status = cmd.execute(cmd_name, args, settings, emborg_opts) except Error as e: exit_status = 2 settings.fail(e, cmd=' '.join(sys.argv)) e.terminate() if exit_status and exit_status > worst_exit_status: worst_exit_status = exit_status # execute the command termination exit_status = cmd.execute_late(cmd_name, args, None, emborg_opts) if exit_status and exit_status > worst_exit_status: worst_exit_status = exit_status except KeyboardInterrupt: display("Terminated by user.") except Error as e: e.report() exit_status = 2 except OSError as e: exit_status = 2 error(os_error(e)) if exit_status and exit_status > worst_exit_status: worst_exit_status = exit_status terminate(worst_exit_status)
def initialize(self, account, field_name, field_key=None): if self.secret: return account_name = account.get_name() account_seed = account.get_seed() if self.master is None: master_seed = account.get_scalar('master_seed', default=None) master_source = account.get_scalar('_master_source_', default=None) else: master_seed = self.master master_source = 'secret' if not master_seed: master_seed = get_setting('user_key') master_source = 'user_key' if not master_seed: try: try: master_seed = getpass.getpass('master seed: ') master_source = 'user' except EOFError: output() if not master_seed: warn("master seed is empty.") except (EOFError, KeyboardInterrupt): terminate() if self.version: version = self.version else: version = account.get_scalar('version', default='') log( 'Generating secret ', '.'.join([str(n) for n in cull([account_name, field_name, field_key, version], remove=(None, ''))]), ', source of master seed: ', master_source, sep='' ) field_key = self.get_key_seed(field_key) request_seed = account.request_seed() interactive_seed = '' if request_seed is True: try: interactive_seed = getpass.getpass('seed: ') except (EOFError, KeyboardInterrupt): terminate() elif callable(request_seed): interactive_seed = request_seed() elif is_str(request_seed): interactive_seed = request_seed elif request_seed: warn("invalid seed.") if request_seed and not interactive_seed: warn("seed is empty.") seeds = [ master_seed, account_seed, field_name, field_key, version, interactive_seed ] self.set_seeds(seeds) assert(self.pool)
def _inform_get_kwargs(self): kwargs = dict(contents=self.expected, wait=self.wait) base = super()._inform_get_kwargs() kwargs['script'] = base.get('script') return cull(kwargs)
def _inform_get_kwargs(self): kwargs = dict(name=self.name, value=self.value) base = super()._inform_get_kwargs() kwargs['script'] = base.get('script') return cull(kwargs)
def ident(self): with Quantity.prefs(spacer=''): return ''.join(cull([self.tokens.name(), self.date])).lower()