mirror of
https://github.com/borgmatic-collective/borgmatic.git
synced 2026-07-22 02:03:01 +02:00
Update the KeePassXC credential hook to support KeePassXC's secret service integration.
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
"working_directory".
|
||||
* #1301: Expand the "patterns_from" and "exclude_from" options to support paths containing tildes
|
||||
and globs.
|
||||
* Update the KeePassXC credential hook to support KeePassXC's secret service integration. See the
|
||||
documentation for more information:
|
||||
https://torsion.org/borgmatic/reference/configuration/credentials/keepassxc/
|
||||
* Add the NEWS changelog file to release tarball (#1298).
|
||||
* Enable reply by email on projects.torsion.org, so replies to notification emails get posted as
|
||||
comments on tickets.
|
||||
|
||||
@@ -3309,24 +3309,32 @@ properties:
|
||||
description: |
|
||||
Command to use instead of "keepassxc-cli".
|
||||
example: /usr/local/bin/keepassxc-cli
|
||||
secret_tool_command:
|
||||
type: string
|
||||
description: |
|
||||
Command to use instead of "secret-tool".
|
||||
example: /usr/local/bin/secret-tool
|
||||
ask_for_password:
|
||||
type: boolean
|
||||
description: |
|
||||
Whether keepassxc-cli should prompt the user for a password.
|
||||
Disabling this is only really useful if you're unlocking
|
||||
your KeePassXC database with a key file instead of a
|
||||
password. Defaults to true.
|
||||
password. Ignored when using KeePassXC's secret service
|
||||
integration. Defaults to true.
|
||||
example: false
|
||||
key_file:
|
||||
type: string
|
||||
description: |
|
||||
Path to a key file for unlocking the KeePassXC database.
|
||||
Ignored when using KeePassXC's secret service integration.
|
||||
example: /path/to/keyfile
|
||||
yubikey:
|
||||
type: string
|
||||
description: |
|
||||
YubiKey slot and optional serial number used to access the
|
||||
KeePassXC database. The format is "<slot[:serial]>", where:
|
||||
KeePassXC database. Ignored when using KeePassXC's secret
|
||||
service integration. The format is "<slot[:serial]>", where:
|
||||
* <slot> is the YubiKey slot number (e.g., `1` or `2`).
|
||||
* <serial> (optional) is the YubiKey's serial number (e.g.,
|
||||
`7370001`).
|
||||
|
||||
@@ -7,19 +7,34 @@ import borgmatic.execute
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
SECRET_SERVICE_DATABASE_PATH = 'secret-service'
|
||||
|
||||
|
||||
def load_credential(hook_config, config, credential_parameters):
|
||||
'''
|
||||
Given the hook configuration dict, the configuration dict, and a credential parameters tuple
|
||||
containing a KeePassXC database path and an attribute name to load, run keepassxc-cli to fetch
|
||||
the corresponding KeePassXC credential and return it.
|
||||
the corresponding KeePassXC credential and return it. Or use secret-tool if the database path
|
||||
is "secret-service", indicating that KeePassXC's secret service integration should be used
|
||||
instead.
|
||||
|
||||
Raise ValueError if keepassxc-cli can't retrieve the credential.
|
||||
Raise ValueError if keepassxc-cli or secret-tool can't retrieve the credential.
|
||||
'''
|
||||
try:
|
||||
(database_path, attribute_name) = credential_parameters
|
||||
except ValueError:
|
||||
raise ValueError(f'Invalid KeePassXC credential parameters: {credential_parameters}')
|
||||
|
||||
if database_path == SECRET_SERVICE_DATABASE_PATH:
|
||||
command = (
|
||||
*shlex.split((hook_config or {}).get('secret_tool_command', 'secret-tool')),
|
||||
*('lookup', 'Path', attribute_name),
|
||||
)
|
||||
|
||||
return '\n'.join(borgmatic.execute.execute_command_and_capture_output(command)).rstrip(
|
||||
os.linesep
|
||||
)
|
||||
|
||||
expanded_database_path = os.path.expanduser(database_path)
|
||||
|
||||
if not os.path.exists(expanded_database_path):
|
||||
|
||||
@@ -43,14 +43,15 @@ postgresql_databases:
|
||||
```
|
||||
|
||||
|
||||
### Custom command
|
||||
### Custom commands
|
||||
|
||||
You can also optionally override the `keepassxc-cli` command that borgmatic calls to load
|
||||
passwords:
|
||||
You can also optionally override the `keepassxc-cli` or `secret-tool` commands
|
||||
that borgmatic call to load passwords:
|
||||
|
||||
```yaml
|
||||
keepassxc:
|
||||
keepassxc_cli_command: /usr/local/bin/keepassxc-cli
|
||||
secret_tool_command: /usr/local/bin/secret-tool
|
||||
```
|
||||
|
||||
Another example:
|
||||
@@ -98,3 +99,30 @@ keepassxc:
|
||||
The value here is the YubiKey slot number (e.g., `1` or `2`) and optional serial
|
||||
number (e.g., `7370001`) used to access the KeePassXC database. Join the two
|
||||
values with a colon, but omit the colon if you're leaving out the serial number.
|
||||
|
||||
|
||||
### Secret service integration
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 2.1.6</span> borgmatic
|
||||
supports [KeePassXC's secret service
|
||||
integration](https://keepassxc.org/docs/KeePassXC_UserGuide#_secret_service_integration)
|
||||
that integrates with the [freedesktop secret service
|
||||
API](https://specifications.freedesktop.org/secret-service/latest/) and allows
|
||||
clients like borgmatic to access your passwords.
|
||||
|
||||
To use this feature from borgmatic, specify `secret-service` instead of a
|
||||
KeePassXC database path when calling this credential hook. For instance:
|
||||
|
||||
```yaml
|
||||
encryption_passphrase: "{credential keepassxc secret-service borgmatic}"
|
||||
```
|
||||
|
||||
With this in place, borgmatic runs libsecret's `secret-tool` command to retrieve
|
||||
the password titled "borgmatic" on demand. KeePassXC may then prompt you to
|
||||
approve the password access, depending on how you've configured it.
|
||||
|
||||
One benefit of using the KeePassXC's secret service integration like this is
|
||||
that you don't have to type a KeePassXC database passhprase (or use a keyfile)
|
||||
whenever borgmatic accesses your passwords. Instead, you can configure
|
||||
KeePassXC to prompt you to approve or deny each access. Or you can even
|
||||
pre-approve password access to support automated borgmatic runs.
|
||||
|
||||
@@ -31,6 +31,55 @@ def test_load_credential_with_missing_database_raises():
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_secret_service_database_path_fetches_password_via_secret_tool():
|
||||
flexmock(module.os.path).should_receive('expanduser').never()
|
||||
flexmock(module.os.path).should_receive('exists').never()
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output',
|
||||
).with_args(
|
||||
(
|
||||
'secret-tool',
|
||||
'lookup',
|
||||
'Path',
|
||||
'mypassword',
|
||||
),
|
||||
).and_yield('password').once()
|
||||
|
||||
assert (
|
||||
module.load_credential(
|
||||
hook_config={},
|
||||
config={},
|
||||
credential_parameters=('secret-service', 'mypassword'),
|
||||
)
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_secret_service_database_path_and_secret_tool_command_calls_it():
|
||||
flexmock(module.os.path).should_receive('expanduser').never()
|
||||
flexmock(module.os.path).should_receive('exists').never()
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output',
|
||||
).with_args(
|
||||
(
|
||||
'/usr/local/bin/secret-tool',
|
||||
'--some-option',
|
||||
'lookup',
|
||||
'Path',
|
||||
'mypassword',
|
||||
),
|
||||
).and_yield('password').once()
|
||||
|
||||
assert (
|
||||
module.load_credential(
|
||||
hook_config={'secret_tool_command': '/usr/local/bin/secret-tool --some-option'},
|
||||
config={},
|
||||
credential_parameters=('secret-service', 'mypassword'),
|
||||
)
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_present_database_fetches_password_from_keepassxc():
|
||||
flexmock(module.os.path).should_receive('expanduser').with_args('database.kdbx').and_return(
|
||||
'database.kdbx',
|
||||
|
||||
Reference in New Issue
Block a user