abraxas

collaborative password utility

Author:

Kale and Ken Kundert <abraxas@nurdletech.com>

Date:

2016-08-14

Version:

1.8

Manual section:

3

DESCRIPTION

The API to Abraxas will be simply demonstrated by example.

archive

This program is used to generate an encrypted file that includes the account numbers and login information for essential accounts. The resulting file could be sent to your Executor or it could be printed and saved in a safe place such as a safe deposit box. The idea is that this information would help whoever needed to access your accounts in case something happened to you.

Here is the archive script:

#!/bin/env python3

from __future__ import print_function, division
from abraxas import PasswordGenerator, PasswordError, Logging
from textwrap import indent
import gnupg
import sys

filename = 'kids.gpg'
recipients = [
    'me@myfamily.name',
    'son@myfamily.name',
    'daughter@myfamily.name']
accounts = [
    ('login', 'Login'),
    ('disk', 'Disk encryption'),
    ('gpg', 'GPG'),
    ('boa', 'Bank of America'),
    ('tdwaterhouse', 'TD Waterhouse')]

try:
    logger = Logging(exception=PasswordError)
    pw = PasswordGenerator(logger=logger)
    pw.read_accounts()

    lines = []
    for name, description in accounts:
        lines += ["%s:" % (description if description else name)]
        acct = pw.get_account(name)

        # Remarks
        remarks = acct.get_field('remarks')
        if remarks:
            if '\n' in remarks:
                lines += ["    remarks:"]
                lines += [indent(remarks.strip(), '        ')]
            else:
                lines += ["    remarks: " + remarks.strip()]

        # Account number
        account = acct.get_field('account')
        if account:
            if type(account) == list:
                lines += ["    account numbers:"]
                lines += ["        %s" % ',\n        '.join(account)]
            else:
                lines += ["    account number:", account]

        # Username
        username = acct.get_field('username')
        if username:
            lines += ["    username:", username]

        # Password
        password = pw.generate_password()
        if password:
            lines += ["    password:", password]

        # Security questions
        number = 0
        security_questions = []
        while True:
            try:
                question, answer = pw.generate_answer(number)
                security_questions += ["        %s ==> %s" % (question, answer)]
                number += 1
            except PasswordError:
                break
        if security_questions:
            lines += ['    security questions:']
            lines += security_questions

        lines += []

    gpg = gnupg.GPG()
    encrypted = gpg.encrypt('\n'.join(lines), recipients)
    if not encrypted.ok:
        sys.exit("%s: unable to encrypt.\n%s" % (filename, encrypted.stderr))
    try:
        with open(filename, 'w') as file:
            file.write(str(encrypted))
        print("%s: created." % filename)
    except IOError as err:
        sys.exit('%s: %s.' % (err.filename, err.strerror))

except KeyboardInterrupt:
    sys.exit('Killed by user')
except PasswordError as err:
    sys.exit(str(err))

The program starts by creating a logger. Normally this is not necessary. When you run PasswordGenerator() without passing in a logger the default logger is created for you. However, the default logger does not throw exceptions. Instead, when a problem occurs an error message is printed to standard error and the program exits. However, this utility needs exceptions to be caught and handled, and so in this case a logger is explicitly created and PasswordError is passed in. In this way, Abraxas does not exit on an error, instead it throws a PasswordError.

mountall

Here is a program that mounts a series of directories. It differs from the above script in that is uses autotype, which it accesses through AutotypeWriter. Specifically, the program never requests a password directly from Abraxas. Instead, the PasswordGenerator object is passed in when creating a AutotypeWriter object. It then queries the generator directly for the password and then gets it directly to the user.

Mountall uses sudo, which requires a password the first time it is run, and it runs mount for each directory, which requires a password each time it is run.

Here is the mountall script:

#!/bin/env python

from __future__ import print_function, division
from fileutils import expandPath, makePath, ShellExecute as Execute, ExecuteError
from sys import exit
from os import fork
from time import sleep
from abraxas import PasswordGenerator, AutotypeWriter, PasswordError

shares = {
    'music': 'audio',
    'lib/passwords': True,
    'business': True,
    'consulting': True,
    'home': True,
    'personal': True,
    'photos': True,
    'profession': True,
    'reference': True}

def run_cmd_with_password(cmd, pw_writer):
    try:
        if (fork()):
            Execute(cmd)
        else:
            sleep(1)
            pw_writer.write_autotype()
            pw_writer.process_output()
            exit()
    except PasswordError as err:
        exit(err.message)

try:
    # Open the password generator
    pw = PasswordGenerator()
    pw.read_accounts()
    writer = AutotypeWriter(pw)

    # Clear out any saved sudo credentials. This is needed so that
    # we can be sure the next run of sudo requests a password.
    # Without this, the password that is autotyped may be exposed.
    Execute('sudo -K')

    # Get the login password
    pw.get_account('login')

    # Run sudo so that it requests the password and sets the
    # credentials. In this way the subsequent calls to sudo will not
    # request a password.
    run_cmd_with_password('sudo true', writer)

    # Get the Samba password
    pw.get_account('dgc21')

    for src, dest in shares.items():
        if dest == True:
            dest = src
        absdest = expandPath(makePath('~', dest))
        mountpoint = pipe('mountpoint -q %s' % absdest, accept=(0,1))
        if mountpoint.status:
            print("Mounting %s to %s" % (src, absdest))
            run_cmd_with_password('sudo mount %s' % (absdest), writer)
        else:
            print("Skipping %s (already mounted)" % (dest))
except KeyboardInterrupt:
    exit('Killed by user')
except ExecuteError as err:
    exit(str(err))
except PasswordError, err:
    sys.exit(str(err))

The program starts by instantiating both the PasswordGenerator and the AutotypeWriter class. The PasswordGenerator class is responsible for generating the password and AutotypeWriter gets it to the user. In this case the autotype facility is used to mimic the keyboard. There are other writers available for writing to a TTY, to stdout, and to the system clipboard.

addkeys

This script is used to pre-load a series of SSH keys into the SSH agent. It is stimilar to the above script, except it uses pexpect rather than autotype. This makes it a bit safer because pexpect waits for the expected prompt from ssh-add, and so will not blindly spew out the password if things go wrong:

#!/usr/bin/python3

import pexpect
from abraxas import PasswordGenerator, PasswordError
import sys

keys = [
    # description       keyfile         abraxas account name
    ('primary rsa',     'id-rsa',       'ssh'              ),
    ('primary ed25519', 'id-ed25519',   'ssh'              ),
    ('digitalocean',    'digitalocean', 'do-ssh'           ),
    ('tunnelr',         'tunnelr',      'tunnelr-ssh'      ),
    ('dumper',          'dumper',       'dumper'           ),
    ('github',          'github',       'github-ssh'       ),
]
ssh_dir = '/home/toby/.ssh'

try:
    pw = PasswordGenerator()
    pw.read_accounts()
except PasswordError as error:
    sys.exit(str(error))

for desc, name, acct in keys:
    print('Adding %s ssh key' % desc)
    try:
        acct = pw.get_account(acct)
        password = pw.generate_password()
        sshadd = pexpect.spawn('ssh-add %s/%s' % (ssh_dir, name))
        sshadd.expect(
            'Enter passphrase for %s/%s: ' % (ssh_dir, name),
            timeout=4
        )
        sshadd.sendline(password)
        sshadd.expect(pexpect.EOF)
        sshadd.close()
        if sshadd.exitstatus:
            print('addkeys: ssh-add: unexpected exit status:', sshadd.exitstatus)
    except PasswordError as error:
        sys.exit(str(error))
    except (pexpect.EOF, pexpect.TIMEOUT):
        sys.exit('addkeys: unexpected prompt from ssh-add: %s' % (
            sshadd.before.decode('utf8')
        ))
    except KeyboardInterrupt:
        exit('Killed by user')

SEE ALSO

abraxas(1), abraxas(5)