Load systemd encrypted credentials

This commit is contained in:
cvlc12 2024-07-31 20:24:14 +00:00 committed by cvlc12
parent a4b65cf710
commit 4e833e147a
8 changed files with 77 additions and 4 deletions

2
NEWS
View File

@ -1,4 +1,6 @@
1.8.15.dev0
* #914: Fix a confusing apparent hang when when the repository location changes, and instead
show a helpful error message.
* #919: Clarify the command-line help for the "--config" flag.
* #919: Document a policy for versioning and breaking changes:
https://torsion.org/borgmatic/docs/how-to/upgrade/#versioning-and-breaking-changes

View File

@ -39,8 +39,7 @@ def make_environment(config):
environment_variable_name,
) in DEFAULT_BOOL_OPTION_TO_DOWNCASE_ENVIRONMENT_VARIABLE.items():
value = config.get(option_name)
if value is not None:
environment[environment_variable_name] = 'yes' if value else 'no'
environment[environment_variable_name] = 'yes' if value else 'no'
for (
option_name,

View File

@ -611,6 +611,9 @@ def log_record(suppress_log=False, **kwargs):
return record
BORG_REPOSITORY_ACCESS_ABORTED_EXIT_CODE = 62
def log_error_records(
message, error=None, levelno=logging.CRITICAL, log_command_error_output=False
):
@ -651,6 +654,13 @@ def log_error_records(
)
yield log_record(levelno=levelno, levelname=level_name, msg=str(error))
if error.returncode == BORG_REPOSITORY_ACCESS_ABORTED_EXIT_CODE:
yield log_record(
levelno=levelno,
levelname=level_name,
msg='\nTo work around this, set either the "relocated_repo_access_is_ok" or "unknown_unencrypted_repo_access_is_ok" option to "true", as appropriate.',
)
except (ValueError, OSError) as error:
yield log_record(levelno=levelno, levelname=level_name, msg=str(message))
yield log_record(levelno=levelno, levelname=level_name, msg=str(error))

View File

@ -232,7 +232,11 @@ properties:
passcommand/repokey/keyfile encryption. Note that if both
encryption_passcommand and encryption_passphrase are set, then
encryption_passphrase takes precedence. Defaults to not set.
This can also be used to access encrypted systemd service
credentials (stored as "/etc/credstore.encrypted/borgpw", or as
files in "/etc/credstore.encrypted/borg/").
example: "secret-tool lookup borg-repository repo-name"
example: "cat ${CREDENTIALS_DIRECTORY}/borgpw"
encryption_passphrase:
type: string
description: |

View File

@ -29,6 +29,38 @@ For example, to ask the *Pass* password manager to provide the passphrase:
encryption_passcommand: pass path/to/borg-repokey
```
### Using systemd service credentials
Borgmatic supports using encrypted [credentials](https://systemd.io/CREDENTIALS/).
Save your password as an encrypted credential to `/etc/credstore.encrypted/borgpw`, e.g.,
```
# systemd-ask-password -n | systemd-creds encrypt - /etc/credstore.encrypted/borgpw
```
Note that the name `borgpw` is hardcoded in the systemd service file.
If you use multiple different passwords, save them as encrypted credentials to `/etc/credstore.encrypted/borg/`, e.g.,
```
# mkdir /etc/credstore.encrypted/borg
# systemd-ask-password -n | systemd-creds encrypt --name=borg_backupserver1 - /etc/credstore.encrypted/borg/backupserver1
# systemd-ask-password -n | systemd-creds encrypt --name=borg_pw2 - /etc/credstore.encrypted/borg/pw2
...
```
Ensure that the file names, (e.g. `backupserver1`) match the corresponding part of
the `--name` option *after* the underscore (_). The `borg` folder is hardcoded in the systemd service file.
Then uncomment or use one of the following in your configuration file. Adjust `borg_backupserver1`
according to the name given to the credential.
```yaml
encryption_passcommand: "cat ${CREDENTIALS_DIRECTORY}/borgpw"
encryption_passcommand: "cat ${CREDENTIALS_DIRECTORY}/borg_backupserver1"
```
### Environment variable interpolation
<span class="minilink minilink-addedin">New in version 1.6.4</span> borgmatic

View File

@ -9,6 +9,10 @@ ConditionACPower=true
[Service]
Type=oneshot
# Load encrypted credentials.
LoadCredentialEncrypted=borg:/etc/credstore.encrypted/borg/
LoadCredentialEncrypted=borgpw
# Security settings for systemd running as root, optional but recommended to improve security. You
# can disable individual settings if they cause problems for your use case. For more details, see
# the systemd manual: https://www.freedesktop.org/software/systemd/man/systemd.exec.html

View File

@ -19,11 +19,15 @@ def test_make_environment_with_ssh_command_should_set_environment():
assert environment.get('BORG_RSH') == 'ssh -C'
def test_make_environment_without_configuration_should_not_set_environment():
def test_make_environment_without_configuration_sets_certain_environment_variables():
environment = module.make_environment({})
# borgmatic always sets this Borg environment variable.
assert environment == {'BORG_EXIT_CODES': 'modern'}
assert environment == {
'BORG_EXIT_CODES': 'modern',
'BORG_RELOCATED_REPO_ACCESS_IS_OK': 'no',
'BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK': 'no',
}
def test_make_environment_with_relocated_repo_access_true_should_set_environment_yes():

View File

@ -1168,6 +1168,24 @@ def test_log_error_records_generates_output_logs_for_called_process_error_with_s
assert any(log for log in logs if 'error output' in str(log))
def test_log_error_records_generates_work_around_output_logs_for_called_process_error_with_repository_access_aborted_exit_code():
flexmock(module).should_receive('log_record').replace_with(dict).times(4)
flexmock(module.logger).should_receive('getEffectiveLevel').and_return(logging.WARNING)
logs = tuple(
module.log_error_records(
'Error',
subprocess.CalledProcessError(
module.BORG_REPOSITORY_ACCESS_ABORTED_EXIT_CODE, 'ls', 'error output'
),
)
)
assert {log['levelno'] for log in logs} == {logging.CRITICAL}
assert any(log for log in logs if 'error output' in str(log))
assert any(log for log in logs if 'To work around this' in str(log))
def test_log_error_records_splits_called_process_error_with_multiline_ouput_into_multiple_logs():
flexmock(module).should_receive('log_record').replace_with(dict).times(4)
flexmock(module.logger).should_receive('getEffectiveLevel').and_return(logging.WARNING)