From 578b76af3ab5ed09dfb8bfc4bd581b2be849a129 Mon Sep 17 00:00:00 2001 From: TuXicc Date: Wed, 31 May 2017 19:25:21 +0200 Subject: [PATCH] Added BORG_PASSCOMMAND environment variable (#2573) --- docs/faq.rst | 10 +++++----- docs/quickstart.rst | 2 ++ docs/usage_general.rst.inc | 12 ++++++++++-- src/borg/crypto/key.py | 24 ++++++++++++++++++++++-- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index c251417c5..cb9c3a2a2 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -257,11 +257,11 @@ Security How can I specify the encryption passphrase programmatically? ------------------------------------------------------------- -The encryption passphrase can be specified programmatically using the -`BORG_PASSPHRASE` environment variable. This is convenient when setting up -automated encrypted backups. Another option is to use -key file based encryption with a blank passphrase. See -:ref:`encrypted_repos` for more details. +The encryption passphrase or a command to retrieve the passphrase can be +specified programmatically using the `BORG_PASSPHRASE` or `BORG_PASSCOMMAND` +environment variables. This is convenient when setting up automated encrypted +backups. Another option is to use key file based encryption with a blank passphrase. +See :ref:`encrypted_repos` for more details. .. _password_env: .. note:: Be careful how you set the environment; using the ``env`` diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 466b8306f..ffc42e350 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -86,6 +86,8 @@ backed up and that the ``prune`` command is keeping and deleting the correct bac # Setting this, so you won't be asked for your repository passphrase: export BORG_PASSPHRASE='XYZl0ngandsecurepa_55_phrasea&&123' + # or this to ask an external program to supply the passphrase: + export BORG_PASSCOMMAND='pass show backup' # some helpers and error handling: function info () { echo -e "\n"`date` $@"\n" >&2; } diff --git a/docs/usage_general.rst.inc b/docs/usage_general.rst.inc index 2b8c630e1..726ac624c 100644 --- a/docs/usage_general.rst.inc +++ b/docs/usage_general.rst.inc @@ -131,12 +131,20 @@ General: can either leave it away or abbreviate as `::`, if a positional parameter is required. BORG_PASSPHRASE When set, use the value to answer the passphrase question for encrypted repositories. - It is used when a passphrase is needed to access a encrypted repo as well as when a new + It is used when a passphrase is needed to access an encrypted repo as well as when a new passphrase should be initially set when initializing an encrypted repo. See also BORG_NEW_PASSPHRASE. + BORG_PASSCOMMAND + When set, use the standard output of the command (trailing newlines are stripped) to answer the + passphrase question for encrypted repositories. + It is used when a passphrase is needed to access an encrypted repo as well as when a new + passphrase should be initially set when initializing an encrypted repo. + If BORG_PASSPHRASE is also set, it takes precedence. + See also BORG_NEW_PASSPHRASE. BORG_NEW_PASSPHRASE When set, use the value to answer the passphrase question when a **new** passphrase is asked for. - This variable is checked first. If it is not set, BORG_PASSPHRASE will be checked also. + This variable is checked first. If it is not set, BORG_PASSPHRASE and BORG_PASSCOMMAND will also + be checked. Main usecase for this is to fully automate ``borg change-passphrase``. BORG_DISPLAY_PASSPHRASE When set, use the value to answer the "display the passphrase for verification" question when defining a new passphrase for encrypted repositories. diff --git a/src/borg/crypto/key.py b/src/borg/crypto/key.py index 37cf3f552..72c260a1b 100644 --- a/src/borg/crypto/key.py +++ b/src/borg/crypto/key.py @@ -3,6 +3,7 @@ import getpass import os import sys import textwrap +import subprocess from binascii import a2b_base64, b2a_base64, hexlify from hashlib import sha256, sha512, pbkdf2_hmac from hmac import HMAC, compare_digest @@ -29,7 +30,11 @@ PREFIX = b'\0' * 8 class PassphraseWrong(Error): - """passphrase supplied in BORG_PASSPHRASE is incorrect""" + """passphrase supplied in BORG_PASSPHRASE or by BORG_PASSCOMMAND is incorrect.""" + + +class PasscommandFailure(Error): + """passcommand supplied in BORG_PASSCOMMAND failed: {}""" class PasswordRetriesExceeded(Error): @@ -413,7 +418,22 @@ class Passphrase(str): @classmethod def env_passphrase(cls, default=None): - return cls._env_passphrase('BORG_PASSPHRASE', default) + passphrase = cls._env_passphrase('BORG_PASSPHRASE', default) + if passphrase is not None: + return passphrase + passphrase = cls.env_passcommand() + if passphrase is not None: + return passphrase + + @classmethod + def env_passcommand(cls, default=None): + passcommand = os.environ.get('BORG_PASSCOMMAND', None) + if passcommand is not None: + try: + passphrase = subprocess.check_output(passcommand.split(), universal_newlines=True) + except (subprocess.CalledProcessError, FileNotFoundError) as e: + raise PasscommandFailure(e) + return cls(passphrase.rstrip('\n')) @classmethod def env_new_passphrase(cls, default=None):