From 0e9193c46bd4ea4d174c0655c2b4de3d347ff66a Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Fri, 14 Nov 2025 21:20:21 -0800 Subject: [PATCH] Add an "ask_for_password" option to the KeePassXC credential hook for disabling KeePassXC's password prompt (#1181). --- NEWS | 2 + borgmatic/config/schema.yaml | 8 ++++ borgmatic/hooks/credential/keepassxc.py | 1 + .../configuration/credentials/keepassxc.md | 47 +++++++++++++++++++ tests/unit/hooks/credential/test_keepassxc.py | 32 +++++++++++++ 5 files changed, 90 insertions(+) diff --git a/NEWS b/NEWS index f39344b8..3eb850d0 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ 2.0.12.dev0 + * #1181: Add an "ask_for_password" option to the KeePassXC credential hook for disabling + KeePassXC's password prompt, e.g. if you're only using a key file to decrypt your database. * In the SQLite database hook, run SQLite such that it exits upon encountering an error instead of, you know, not doing that. * Add documentation on repositories, including SSH, Rclone, S3, and B2: diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index 2fa06527..dbcc54a3 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -3172,6 +3172,14 @@ properties: description: | Command to use instead of "keepassxc-cli". example: /usr/local/bin/keepassxc-cli + 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. + example: false key_file: type: string description: | diff --git a/borgmatic/hooks/credential/keepassxc.py b/borgmatic/hooks/credential/keepassxc.py index c3605fcf..7698bd72 100644 --- a/borgmatic/hooks/credential/keepassxc.py +++ b/borgmatic/hooks/credential/keepassxc.py @@ -29,6 +29,7 @@ def load_credential(hook_config, config, credential_parameters): command = ( tuple(shlex.split((hook_config or {}).get('keepassxc_cli_command', 'keepassxc-cli'))) + ('show', '--show-protected', '--attributes', 'Password') + + (('--no-password',) if not hook_config.get('ask_for_password', True) else ()) + ( ('--key-file', hook_config['key_file']) if hook_config and hook_config.get('key_file') diff --git a/docs/reference/configuration/credentials/keepassxc.md b/docs/reference/configuration/credentials/keepassxc.md index fa2dabf4..80be581c 100644 --- a/docs/reference/configuration/credentials/keepassxc.md +++ b/docs/reference/configuration/credentials/keepassxc.md @@ -42,6 +42,9 @@ postgresql_databases: password: "{credential keepassxc /etc/keys.kdbx database}" ``` + +### Custom command + You can also optionally override the `keepassxc-cli` command that borgmatic calls to load passwords: @@ -49,3 +52,47 @@ passwords: keepassxc: keepassxc_cli_command: /usr/local/bin/keepassxc-cli ``` + +Another example: + +```yaml +keepassxc: + keepassxc_cli_command: docker exec keepassxc keepassxc-cli +``` + +### Key file + +KeePassXC supports unlocking a database with a separate [key +file](https://keepassxc.org/docs/#faq-keyfile-howto) instead of or in addition +to a password. To configure borgmatic for that, use the `key_file` option: + +```yaml +keepassxc: + key_file: /path/to/keyfile +``` + +By default, keepassxc-cli prompts the user for the password to unlock a +database. But if you only want to provide a key file to unlock your database and +not a password, for instance to support unattended backups, use the +`ask_for_password` option: + +```yaml +keepassxc: + ask_for_password: false + key_file: /path/to/keyfile +``` + +### YubiKey + +KeePassXC also supports unlocking a database with the help of [a +YubiKey](https://keepassxc.org/docs/#faq-yubikey-2fa). To configure borgmatic +for that, use the `yubikey` option: + +```yaml +keepassxc: + yubikey: 1:7370001 +``` + +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. diff --git a/tests/unit/hooks/credential/test_keepassxc.py b/tests/unit/hooks/credential/test_keepassxc.py index a9275019..345830d9 100644 --- a/tests/unit/hooks/credential/test_keepassxc.py +++ b/tests/unit/hooks/credential/test_keepassxc.py @@ -151,6 +151,38 @@ def test_load_credential_with_key_file(): ) +def test_load_credential_with_key_file_and_ask_for_password_false(): + flexmock(module.os.path).should_receive('expanduser').with_args('database.kdbx').and_return( + 'database.kdbx', + ) + flexmock(module.os.path).should_receive('exists').and_return(True) + flexmock(module.borgmatic.execute).should_receive( + 'execute_command_and_capture_output', + ).with_args( + ( + 'keepassxc-cli', + 'show', + '--show-protected', + '--attributes', + 'Password', + '--no-password', + '--key-file', + '/path/to/keyfile', + 'database.kdbx', + 'mypassword', + ), + ).and_return('password').once() + + assert ( + module.load_credential( + hook_config={'key_file': '/path/to/keyfile', 'ask_for_password': False}, + config={}, + credential_parameters=('database.kdbx', 'mypassword'), + ) + == 'password' + ) + + def test_load_credential_with_yubikey(): flexmock(module.os.path).should_receive('expanduser').with_args('database.kdbx').and_return( 'database.kdbx',