Esempio n. 1
0
def get_all_pubkeys(fps, ldap, use_agent, gpg_path, gnupghome):
    """
    Return dict {"email": ["public key in PEM format",...]}
    """

    r = {}

    with GPG(use_agent, gpg_path, gnupghome) as gpg:
        for fp in fps:
            debug(
                'Fetching public key with fingerprint {} (from configuration file).'
                .format(fp))
            key = gpg.find_key(fp)
            if key is None:
                sys.exit('Key with fingerprint {} not found.'.format(fp))

            uid = key['uids'][0]
            key_data = gpg.gpg.export_keys(fp)
            if uid not in r:
                r[uid] = []
            r[uid].append(base64.b64encode(key_data.encode('utf-8')))

    if ldap is not None:
        debug('Fetching public keys from LDAP')
        try:
            r.update(
                get_ldap_group_keys(
                    ldap,
                    ldap.get('bind_pw',
                             getpass.getpass(prompt='LDAP password:'))))
        except RuntimeError as e:
            sys.exit(str(e))

    return r
Esempio n. 2
0
def _sync_pws(datapath, force, dec_gpg, enc_gpg):
    accounts = get_all_passwords(datapath)

    git = Git(datapath)
    with GitTransaction(git):
        num = 0
        if git.has_origin():
            git.rebase_origin_master()
        for (host, user, path) in accounts.iterate():
            # There are three cases where the file needs to be reencrypted:
            #   1. The force flag is set
            #   2. The file is encrypted to a key that is no longer available,
            #      or expected as a recipient
            #   3. The list of recipients do not match the expected recipient list
            (rec_fps, rec_not_found) = enc_gpg.get_file_recipients(path)
            if force or rec_not_found or sorted(rec_fps) != sorted(
                    enc_gpg.get_recipient_fps()):
                debug('Need to reencrypt {}'.format(path))
                encpw = enc_gpg.encrypt(dec_gpg.decrypt_file(path))
                write_and_add(git, path, encpw, True)
                num += 1

        if num > 0:
            uids = ''.join(
                ['    - {}\n'.format(x) for x in enc_gpg.get_recipient_uids()])
            git.commit(
                "Synchronized and reencrypted {} passwords to {} recipient{}{}\n\n{}\n\n{}"
                .format(num, enc_gpg.get_num_recipients(),
                        's' if enc_gpg.get_num_recipients() > 1 else '',
                        ' (forced)' if force else '', uids, get_version()))
            if git.has_origin():
                git.push_master()
    return num
Esempio n. 3
0
def sync_pws(cfg, args):
    if 'ldap' not in cfg:
        ldap = None
    else:
        ldap = cfg['ldap']

    keys = get_all_pubkeys(get_fps_from_conf(cfg), ldap,
                           cfg['gnupg'].getboolean('use_agent'),
                           cfg['gnupg']['gpg_path'], cfg['gnupg']['home'])

    with GPG(gpg_path=cfg['gnupg']['gpg_path'], use_agent=False) as enc_gpg:
        for email, fps in keys.items():
            debug('Encrypting to {} ({} key{})'.format(
                email, len(fps), 's' if len(keys) > 1 else ''))
            for fp in fps:
                enc_gpg.add_recipient(fp)

        with GPG(use_agent=cfg['gnupg'].getboolean('use_agent'),
                 gpg_path=cfg['gnupg']['gpg_path'],
                 gnupghome=cfg['gnupg']['home']) as dec_gpg:
            if not cfg['gnupg'].getboolean('use_agent'):
                # gpg-agent should automatically popup a different password dialog so
                # we should only ask for the password if we're not using it
                dec_gpg.set_passphrase(args.gnupgpass)

            num = attempt_retry(_sync_pws, cfg['global']['datapath'],
                                args.force, dec_gpg, enc_gpg)

    if num == 0:
        print("No synchronization necessary, recipient lists were correct.")
    else:
        print("Successfully reencrypted {} passwords to {} recipients.".format(
            num, enc_gpg.get_num_recipients()))
Esempio n. 4
0
def _add_pw(host, user, password, datapath, keys, exist_ok, gpg_path,
            gnupghome):
    accounts = get_all_passwords(datapath)

    if not exist_ok and accounts.exists(host, user):
        sys.exit(
            "Account {} on {} already exists, use replace instead.".format(
                user, host))

    if password is None:
        new_pass = getpass.getpass('Enter password to store:')
        if getpass.getpass('Enter it again to verify:') != new_pass:
            sys.exit("Passwords don't match.")
    else:
        new_pass = password

    # We are only encrypting and using different keyrings, so do not use the
    # users gpg-agent even if we were instructed to do so.
    with GPG(False, gpg_path, gnupghome) as gpg:
        for email, fp_list in keys.items():
            debug('Encrypting to {} ({} key{})'.format(
                email, len(fp_list), 's' if len(fp_list) > 1 else ''))
            for fp in fp_list:
                gpg.add_recipient(fp)

        # Adding a trailing newline makes it prettier if someone decodes using
        # regular command line gnupg.
        encpw = gpg.encrypt('{}\n'.format(new_pass))

    path = hostuser_to_path(datapath, host, user)
    attempt_retry(do_add, datapath, path, host, user, encpw, exist_ok)
    return gpg.get_num_recipients()
Esempio n. 5
0
 def __enter__(self):
     debug('Locking datastore at {}'.format(os.path.dirname(self.path)))
     try:
         f = open(self.path, 'x')
     except FileExistsError:
         f = open(self.path, 'r')
     fcntl.flock(f, fcntl.LOCK_EX)
     self.f = f
Esempio n. 6
0
 def __exit__(self, type, value, tb):
     if tb is not None:
         # An exception occured, roll back. The exception will be re-raised
         # once this handler is complete.
         debug('Exception occured, aborting git transaction to saved state')
         self.rollback()
     else:
         debug('Git transaction completed cleanly')
     self.git.rm_tag(self.tag)
Esempio n. 7
0
    def decrypt(self, data):
        debug('Decrypting using gnupg homedir {}'.format(self.gnupghome))
        if not self.use_agent:
            if self.passphrase is None:
                raise RuntimeError("No GPG password set")
            r = self.gpg.decrypt(data, passphrase=self.passphrase)
        else:
            r = self.gpg.decrypt(data)

        if not r.ok:
            raise RuntimeError("Decryption failed ({})".format(r.status))
        return str(r)
Esempio n. 8
0
def get_dn_attribute(conn, dn, filtr, attr):
    """
    Return an attribute for the LDAP entry specified by dn.
    """
    debug("Getting LDAP attribute '{}' for DN={} with filter '{}'".format(
        attr, dn, filtr))
    r = conn.search(dn, filtr, attributes=[attr])
    if r is False or len(conn.entries) != 1:
        raise RuntimeError(
            "dn '{}' filter '{}' return did not return exactly one hit".format(
                dn, filtr))

    if attr not in conn.entries[0]:
        return []
    return [x for x in conn.entries[0][attr]]
Esempio n. 9
0
def attempt_retry(fnc, *args, **kwargs):
    # This is useful because e.g. some git transactions will fail if multiple
    # people are committing and rebasing simultaneously. Retrying them a few
    # times make sense.
    attempt = 0
    max_attempts = 5
    while True:
        attempt += 1
        debug('Attempting {}, attempt {}/{}'.format(getattr(fnc, '__name__'),
                                                    attempt, max_attempts))
        try:
            return fnc(*args, **kwargs)
        except Exception as e:
            debug('Attempt failed, caught {}: {}'.format(type(e), str(e)))
            if attempt >= 5:
                raise
            time.sleep(0.5)
Esempio n. 10
0
    def run_git(self, cmdl):
        cmdl.insert(0, self.git)
        stdout = []
        stderr = []
        debug("Running '{}' in {}".format(' '.join(cmdl).replace('\n', '\\n'),
                                          self.repo_path))
        p = subprocess.Popen(cmdl,
                             cwd=self.repo_path,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        try:
            (stdout, stderr) = p.communicate(timeout=60)
        except subprocess.TimeoutExpired:
            sys.exit("git commandline '{}' timed out".format(cmdl))
        output = '{}{}'.format(
            stdout.decode('utf-8') if stdout else '',
            stderr.decode('utf-8') if stderr else '')
        if p.returncode != 0:
            if not self.silent:
                print(output)
            raise subprocess.CalledProcessError(cmd=cmdl,
                                                returncode=p.returncode,
                                                output=output)

        # The output from git tends to be very verbose so we make it a bit
        # more dense here from debugging purposes.
        debug('git returned success: {}'.format(
            '(no output)' if not output else ''))
        for line in output.split('\n'):
            if line == '' or line.isspace():
                continue
            debug('    {}'.format(line))

        return output
Esempio n. 11
0
def _rm_pw(datapath, host, user, pwfile):
    def do_rm(pwfile, host, user):
        git = Git(datapath)
        with GitTransaction(git):
            if git.has_origin():
                git.rebase_origin_master()
            git.rm(pwfile)
            git.commit("{}/{}: remove\n\n{}".format(host, user, get_version()))
            if git.has_origin():
                git.push_master()

    debug("Removing password for account '{}/{}'".format(host, user))

    attempt_retry(do_rm, pwfile, host, user)
    try:
        os.rmdir(os.path.dirname(pwfile))
    except OSError as e:
        if e.errno == errno.ENOTEMPTY:
            debug("More accounts exist for '{}', not removing host.".format(
                host))
    else:
        debug("No more accounts exist for '{}', host removed.".format(host))
Esempio n. 12
0
 def __exit__(self, type, value, tb):
     debug('Unlocking datastore at {}'.format(os.path.dirname(self.path)))
     # We can never remove the lock file or we might trigger a lock race
     self.f.close()
Esempio n. 13
0
 def set_passphrase(self, pw=None):
     if pw is None:
         pw = getpass.getpass('Enter GPG password:'******'Passphrase already supplied')
     self.passphrase = pw
Esempio n. 14
0
 def decrypt_file(self, path):
     debug('Trying to decrypt file {}'.format(path))
     with open(path, 'rb') as f:
         data = f.read()
     return self.decrypt(data)
Esempio n. 15
0
 def __enter__(self):
     debug('Starting git transaction {}'.format(self.tag))
     self.git.tag(self.tag)