mirror of
https://github.com/borgmatic-collective/borgmatic.git
synced 2026-07-30 21:33:00 +02:00
Add loading of systemd credentials even when running borgmatic outside of a systemd service (#1123).
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
2.0.9.dev0
|
||||
* #1123: Add loading of systemd credentials even when running borgmatic outside of a systemd
|
||||
service.
|
||||
* #1149: Add support for Python 3.14.
|
||||
* #1149: Include automated tests in the source dist tarball uploaded to PyPI.
|
||||
|
||||
|
||||
@@ -3002,6 +3002,25 @@ properties:
|
||||
description: |
|
||||
Configuration for integration with Linux LVM (Logical Volume
|
||||
Manager).
|
||||
systemd:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
systemd_creds_command:
|
||||
type: string
|
||||
description: |
|
||||
Command to use instead of "systemd-creds". Only used as a
|
||||
fallback when borgmatic is run outside of a systemd service.
|
||||
example: /usr/local/bin/systemd-creds
|
||||
encrypted_credentials_directory:
|
||||
type: string
|
||||
description: |
|
||||
Directory containing encrypted credentials for
|
||||
"systemd-creds" to use instead of
|
||||
"/etc/credstore.encrypted".
|
||||
example: /path/to/credstore.encrypted
|
||||
description: |
|
||||
Configuration for integration with systemd credentials.
|
||||
container:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
|
||||
import borgmatic.execute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -24,15 +27,28 @@ def load_credential(hook_config, config, credential_parameters):
|
||||
|
||||
raise ValueError(f'Cannot load invalid credential name: "{name}"')
|
||||
|
||||
if not CREDENTIAL_NAME_PATTERN.match(credential_name):
|
||||
raise ValueError(f'Cannot load invalid credential name "{credential_name}"')
|
||||
|
||||
credentials_directory = os.environ.get('CREDENTIALS_DIRECTORY')
|
||||
|
||||
if not credentials_directory:
|
||||
raise ValueError(
|
||||
f'Cannot load credential "{credential_name}" because the systemd CREDENTIALS_DIRECTORY environment variable is not set',
|
||||
logger.debug(
|
||||
f'Falling back to loading credential "{credential_name}" via systemd-creds because the systemd CREDENTIALS_DIRECTORY environment variable is not set'
|
||||
)
|
||||
|
||||
if not CREDENTIAL_NAME_PATTERN.match(credential_name):
|
||||
raise ValueError(f'Cannot load invalid credential name "{credential_name}"')
|
||||
command = (
|
||||
*shlex.split((hook_config or {}).get('systemd_creds_command', 'systemd-creds')),
|
||||
'decrypt',
|
||||
os.path.join(
|
||||
(hook_config or {}).get(
|
||||
'encrypted_credentials_directory', '/etc/credstore.encrypted'
|
||||
),
|
||||
credential_name,
|
||||
),
|
||||
)
|
||||
|
||||
return borgmatic.execute.execute_command_and_capture_output(command).rstrip(os.linesep)
|
||||
|
||||
try:
|
||||
with open(
|
||||
|
||||
@@ -127,15 +127,26 @@ encryption_passcommand: cat ${CREDENTIALS_DIRECTORY}/borgmatic_backupserver1
|
||||
Adjust `borgmatic_backupserver1` according to the name of the credential and the
|
||||
directory set in the service file.
|
||||
|
||||
Be aware that when using this systemd `{credential ...}` feature, you may no
|
||||
longer be able to run certain borgmatic actions outside of the systemd service,
|
||||
as the credentials are only available from within the context of that service.
|
||||
So for instance, `borgmatic list` necessarily relies on the
|
||||
`encryption_passphrase` in order to access the Borg repository, but `list`
|
||||
shouldn't need to load any credentials for your database or monitoring hooks.
|
||||
<span class="minilink minilink-addedin">New in version 2.0.9</span> When using
|
||||
the systemd `{credential ...}` feature, borgmatic loads systemd credentials even
|
||||
when run outside of a systemd service. This works by falling back to calling
|
||||
`systemd-creds decrypt` instead of reading credentials directly. To customize
|
||||
this behavior, you can override the `systemd-creds` command and/or the
|
||||
credential store directory it uses:
|
||||
|
||||
The one exception is `borgmatic config validate`, which doesn't actually load
|
||||
any credentials and should continue working anywhere.
|
||||
```yaml
|
||||
systemd:
|
||||
systemd_creds_command: /usr/local/bin/systemd-creds
|
||||
encrypted_credentials_directory: /path/to/credstore.encrypted
|
||||
```
|
||||
|
||||
<span class="minilink minilink-addedin">Prior to version 2.0.9</span> The
|
||||
systemd `{credential ...}` feature did not work when run outside of a systemd
|
||||
service. But depending on the borgmatic action invoked and the configuration
|
||||
option where `{credential ...}` was used, you could sometimes get away without
|
||||
working systemd credentials for certain actions. For instance, `borgmatic list`
|
||||
doesn't connect to any databases or monitoring services, and `borgmatic config
|
||||
validate` doesn't use credentials as all.
|
||||
|
||||
|
||||
### Container secrets
|
||||
|
||||
@@ -19,13 +19,60 @@ def test_load_credential_with_invalid_credential_parameters_raises(credential_pa
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_without_credentials_directory_raises():
|
||||
def test_load_credential_without_credentials_directory_falls_back_to_systemd_creds_command():
|
||||
flexmock(module.os.environ).should_receive('get').with_args('CREDENTIALS_DIRECTORY').and_return(
|
||||
None,
|
||||
)
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output'
|
||||
).with_args(('systemd-creds', 'decrypt', '/etc/credstore.encrypted/mycredential')).and_return(
|
||||
'password'
|
||||
).once()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
assert (
|
||||
module.load_credential(hook_config={}, config={}, credential_parameters=('mycredential',))
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_without_credentials_directory_calls_custom_systemd_creds_command():
|
||||
flexmock(module.os.environ).should_receive('get').with_args('CREDENTIALS_DIRECTORY').and_return(
|
||||
None,
|
||||
)
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output'
|
||||
).with_args(
|
||||
('/path/to/systemd-creds', '--flag', 'decrypt', '/etc/credstore.encrypted/mycredential')
|
||||
).and_return('password').once()
|
||||
|
||||
assert (
|
||||
module.load_credential(
|
||||
hook_config={'systemd_creds_command': '/path/to/systemd-creds --flag'},
|
||||
config={},
|
||||
credential_parameters=('mycredential',),
|
||||
)
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_without_credentials_directory_uses_custom_encrypted_credentials_directory():
|
||||
flexmock(module.os.environ).should_receive('get').with_args('CREDENTIALS_DIRECTORY').and_return(
|
||||
None,
|
||||
)
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output'
|
||||
).with_args(('systemd-creds', 'decrypt', '/my/credstore.encrypted/mycredential')).and_return(
|
||||
'password'
|
||||
).once()
|
||||
|
||||
assert (
|
||||
module.load_credential(
|
||||
hook_config={'encrypted_credentials_directory': '/my/credstore.encrypted'},
|
||||
config={},
|
||||
credential_parameters=('mycredential',),
|
||||
)
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_invalid_credential_name_raises():
|
||||
|
||||
Reference in New Issue
Block a user