/sparekeys

Primary LanguagePythonGNU General Public License v3.0GPL-3.0

Spare Keys

Spare Keys makes and distributes encrypted copies of the files that you would need to recover from a catastrophic hard drive failure, e.g. SSH keys, GPG keys, password vaults, etc. The basic process goes like this:

  • You specify which files you want to keep spare copies of. You can do this by editing a configuration file, or by installing plugins.
  • You specify where you want to store encrypted copies of theses files (e.g. remote hosts, USB drives, etc.), again via configuration files or plugins.
  • Run sparekeys to automatically create, encrypt, and distribute spare copies of the specified files to the specified locations. A decryption script is distributed with the archive, so the only thing you need to remember is the password you used for the encryption.
  • If you ever lose your hard drive, download the most recent archive from any of the backup locations and run the provided decryption script to recover your credentials.
Last release Python version Documentation Test status Test coverage Last commit

Installation

Spare Keys can be installed from PyPI:

$ pip3 install sparekeys

Note that Spare Keys requires python≥3.6.

Usage

To get started, simply run the following command:

$ sparekeys

This will create and execute a default configuration that will save your SSH and GPG credentials. Your credentials won't be copied anywhere, but the path to the encrypted archive will be shown so that you can copy it yourself.

For more information:

$ sparekeys -h

Examples

Below are some example Spare Keys configuration files to help get you started. See the Configuration and Plugins sections for more information on these options.

Copy SSH and GPG keys to a remote host via scp:

[plugins]
archive = ['ssh', 'gpg']
publish = ['scp']

[publish.scp]
host = 'alice@example.com'

Copy SSH and GPG keys to a USB drive mounted at /mnt/usb:

[plugins]
archive = ['ssh', 'gpg']
publish = ['mount']

[publish.mount]
drive = '/mnt/usb'

Archive SSH keys, GPG keys, and a cryptocurrency wallet:

[plugins]
archive = ['ssh', 'gpg', 'file']

# There isn't a built-in plugin for wallets (yet), so instead use the "file"
# plugin to manually specify files to copy.
[archive.file]
src = '~/.config/cryptocurrency'

Use your avendesora "login" credentials to encrypt the archive. As a fallback, prompt for a password (getpass):

[plugins]
archive = ['ssh', 'gpg']
auth = ['avendesora', 'getpass']

[auth.avendesora]
account = 'login'

Configuration

The configuration file is based on the TOML file format. On Linux systems, it can be found at:

~/.config/sparekeys/config.toml

Broadly, you need to enable any plugins use with to use, and you need to configure any plugins that require extra information:

## Enable plugins ###############################################

[plugins]
# When creating the archive, use the SSH and GPG plugins.
archive = ['ssh', 'gpg']

# When publishing the archive, use the 'scp' and 'mount' plugins:
publish = ['scp', 'mount']

## Configure plugins ############################################

# The SSH and GPG plugins require no further information.

# The 'scp' plugin needs the address of a remote host:
[publish.scp]
host = 'alice@example.com'

# The 'mount' plugin needs the path of a drive to mount:
[publish.scp]
drive = '/mnt/usb'

You can get a list of installed plugins by running sparekeys plugins. More information on the built-in plugins is available in the Plugins section below. The Plugin API section described how you can make your own plugins.

The [plugins] block:

  • archive (list): A list of plugins to use for finding important files and building the archive. Built-in options include 'ssh', 'gpg', and 'file'.
  • publish (list): A list of plugins to use when copying the encrypted archive to remote destinations. Built-in options include 'scp' and 'mount'
  • auth (list): A list of plugins to query for a password when encrypting archive. The plugins will be invoked in the order specified until a passcode is obtained. Any subsequent plugins will not be invoked. If no authentication plugins are specified, the built-in 'getpass' plugin (which asks for a passcode in the terminal) will be used. If no passcode can be obtained, the archive will not be created.

The configuration blocks:

The remaining blocks provide configuration options specific to individual plugins. The block follow the naming pattern: [STAGE.PLUGIN]. STAGE is the category of plugin, e.g. one of archive, publish, or auth. PLUGIN is the name of the plugin, which could be anything. Within the block go any options relating to the plugin in question. Each plugin understands a different set of options.

Below is an example configuration block for the publish.scp plugin, which describes how to copy the archive to a remote host via scp:

[publish.scp]
host = ['alice@home.net', 'alice@work.com']
remote_dir = 'backup'

It is also possible to specify multiple configuration blocks for any individual plugin (except the authentication plugins). If you do this, the plugin will be executed once for each such block. For example, the following configuration would publish the spare keys to two different directories on two different remote hosts:

[[publish.scp]]
host = 'alice@home.net'
remote_dir = 'backup'

[[publish.scp]]
host = 'alice@work.com'
remote_dir = '/backups/alice/'

Top-level options:

  • archive_name (str, default: '{host}'): A format string that will be used to name each archive. The following values can be substituted using the standrad python formatting syntax:

    • {user}: The name of the logged-in user.
    • {host}: The name of the current machine.
    • {date:YYYYMMDD}: The current date. The characters after the colon specify how the date should be formatted.

Plugins

Spare Keys supports the use of setuptools plugins to customize the backup process. Below are descriptions of all the built-in plugins:

archive.ssh
Copy the .ssh directory into the archive. No configuration options.
archive.gpg
Copy the .gpg directory into the archive. No configuration options.
archive.file

Copy arbitrary files into the archive. This plugin is provided to make it easy to copy valuable files for which devoted plugins are not available. The following option must be configured:

  • src (str or list): One or more paths to copy. The copied file(s) will have the same path relative to the archive as the original file(s) have relative to the home directory.
archive.emborg

Copy files for borg backup and its emborg front-end into the archive. These files include the keys and configuration options necessary to recover your backups. The borg key export command is run to download keys for 'repokey' backups, protecting against corruption in the backup archive.

  • config (str): Name of emborg configuration to use. If not given the default configuration is used.
archive.avendesora

Copy configuration files for the avendesora password manager into the archive.

No configuration options.

publish.scp

Copy the encrypted archive to a remote host via scp. The following configuration options are available:

  • host (str or list, required): The name(s) of the remote host(s) to copy the archive to. Any format understood by SSH is acceptable.
  • remote_dir (str, default: 'backup/sparekeys'): The directory where the spare keys should be stored on the remote host.
publish.mount

Copy the encrypted archive to a mounted/mountable drive. For example, it might be a good idea to copy your keys onto a USB drive which could be stored in a safe-deposit box. The following configuration options are available:

  • drive (str): The path to the mountpoint for the drive, which must be present and configured in /etc/fstab. If the drive is not mounted when Spare Keys runs, Spare Keys will attempt to mount it and will (if successful) unmount it when finished. If the drive is mounted when Spare Keys runs, Spare Keys will leave it mounted.
  • remote_dir (str, default: 'backup/sparekeys'): The directory where the spare keys should be stored on the mounted drive.
auth.getpass
Get a passcode for the archive by prompting for one in the terminal. The passcode is never printed to the terminal and never saved anywhere. This plugin is special in that it is the default if no other authentication plugins are enabled.
auth.avendesora

Get a passcode for the archive from avendesora.

  • account (str): The name of the account to get the passcode for. It's recommended to use a password you have completely memorized (e.g. a login password), because avendesora itself is unlikely to be available to you if you ever need to recover your keys. This configuration option is required.
  • field (str): The name of the account field that contains the password or pass phrase. If not given, avendesora chooses a likely candidate for you.

Plugin API

Plugins can be installed using the setuptools Entry Points API:

setup(
   ...
   entry_points={
       'sparekeys.archive': [
           'spam=package.module:archive_spam',
       ],
       'sparekeys.publish': [
           'spam=package.module:publish_spam',
       ],
       'sparekeys.auth': [
           'spam=package.module:auth_spam',
       ],
   },
   ...
)

Currently, three entry points are supported: sparekeys.archive, sparekeys.publish, and sparekeys.auth. These entry points correspond to the three categories of plugins detailed in the Configuration section above. Each plugin must have a unique name within its category ("spam" in the example above).

An archive plugin must be a function that accepts two arguments:

  • A dictionary with any configuration values specific to the plugin.
  • The path to the archive.

The function must copy any necessary files into the archive, possibly after doing more complicated things like generating or downloading said files. The sparekeys.copy_to_archive() utility is often useful for these plugins. It copies files into the archive such that their path within the archive is the same as their path relative to the home directory. Below is an example that copies ~/.config/spam into the archive:

def archive_spam(config, archive):
    sparekeys.copy_to_archive('~/.config/spam', archive)

A publish plugin must be a function that accepts two arguments:

  • A dictionary with any configuration values specific to the plugin.
  • The path the directory containing the encrypted archive (called archive.tgz.gpg) and the decryption script (called decrypt.sh).

The plugin should copy the encrypted archive to a remote destination. Below is an example that simply copies the archive to ~/spam:

def publish_spam(config, workspace):
   cp(workspace, '~/spam')

An auth plugin must be a function that accepts one argument:

  • A dictionary with any configuration values specific to the plugin.

The plugin should either return a passcode or raise one of the exceptions detailed below. A typical plugin might query a particular password valut, using an account specified in the given configuration. Below is an example that simply returns the string "spam":

def auth_spam(config):
    return "spam"

Exceptions:

Plugins can raise the following exceptions:

  • SkipPlugin: The plugin can't do its job for some reason. A warning will be printed, but the program will continue.
  • PluginConfigError: Something about the plugin's configuration doesn't make sense and/or is missing. The program will be stopped and an informative error will be displayed.
  • PluginError: Something else went wrong. The program will be aborted immediately and an informative error will be displayed..