Skip to content

Commit

Permalink
Revamp systemd credential syntax to be more consistent with constants…
Browse files Browse the repository at this point in the history
… (#966).
  • Loading branch information
witten committed Feb 11, 2025
1 parent 3bc14ba commit 5009629
Show file tree
Hide file tree
Showing 28 changed files with 295 additions and 312 deletions.
4 changes: 2 additions & 2 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
1.9.10
* #966: Add a "!credential" tag for loading systemd credentials into borgmatic configuration
files. See the documentation for more information:
* #966: Add a "{credential ...}" syntax for loading systemd credentials into borgmatic
configuration files. See the documentation for more information:
https://torsion.org/borgmatic/docs/how-to/provide-your-passwords/
* #987: Fix a "list" action error when the "encryption_passcommand" option is set.
* #987: When both "encryption_passcommand" and "encryption_passphrase" are configured, prefer
Expand Down
4 changes: 2 additions & 2 deletions borgmatic/borg/environment.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os

import borgmatic.borg.passcommand
import borgmatic.hooks.credential.tag
import borgmatic.hooks.credential.parse

OPTION_TO_ENVIRONMENT_VARIABLE = {
'borg_base_directory': 'BORG_BASE_DIR',
Expand Down Expand Up @@ -41,7 +41,7 @@ def make_environment(config):
value = config.get(option_name)

if option_name in CREDENTIAL_OPTIONS and value is not None:
value = borgmatic.hooks.credential.tag.resolve_credential(value)
value = borgmatic.hooks.credential.parse.resolve_credential(value)

if value is not None:
environment[environment_variable_name] = str(value)
Expand Down
16 changes: 0 additions & 16 deletions borgmatic/config/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,21 +104,6 @@ def raise_omit_node_error(loader, node):
)


def reserialize_tag_node(loader, tag_node):
'''
Given a ruamel.yaml loader and a node for a tag and value, convert the node back into a string
of the form "!tagname value" and return it. The idea is that downstream code, rather than this
file's YAML loading logic, should be responsible for interpreting this particular tag—since the
downstream code actually understands the meaning behind the tag.
Raise ValueError if the tag node's value isn't a string.
'''
if isinstance(tag_node.value, str):
return f'{tag_node.tag} {tag_node.value}'

raise ValueError(f'The value given for the {tag_node.tag} tag is invalid; use a string instead')


class Include_constructor(ruamel.yaml.SafeConstructor):
'''
A YAML "constructor" (a ruamel.yaml concept) that supports a custom "!include" tag for including
Expand All @@ -137,7 +122,6 @@ def __init__(
config_paths=config_paths,
),
)
self.add_constructor('!credential', reserialize_tag_node)

# These are catch-all error handlers for tags that don't get applied and removed by
# deep_merge_nodes() below.
Expand Down
64 changes: 35 additions & 29 deletions borgmatic/config/schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ properties:
repositories that were initialized with passphrase/repokey/keyfile
encryption. Quote the value if it contains punctuation, so it parses
correctly. And backslash any quote or backslash literals as well.
Defaults to not set. Supports the "!credential" tag.
Defaults to not set. Supports the "{credential ...}" syntax.
example: "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
checkpoint_interval:
type: integer
Expand Down Expand Up @@ -989,28 +989,31 @@ properties:
Username with which to connect to the database. Defaults
to the username of the current user. You probably want
to specify the "postgres" superuser here when the
database name is "all". Supports the "!credential" tag.
database name is "all". Supports the "{credential ...}"
syntax.
example: dbuser
restore_username:
type: string
description: |
Username with which to restore the database. Defaults to
the "username" option. Supports the "!credential" tag.
the "username" option. Supports the "{credential ...}"
syntax.
example: dbuser
password:
type: string
description: |
Password with which to connect to the database. Omitting
a password will only work if PostgreSQL is configured to
trust the configured username without a password or you
create a ~/.pgpass file. Supports the "!credential" tag.
create a ~/.pgpass file. Supports the "{credential ...}"
syntax.
example: trustsome1
restore_password:
type: string
description: |
Password with which to connect to the restore database.
Defaults to the "password" option. Supports the
"!credential" tag.
"{credential ...}" syntax.
example: trustsome1
no_owner:
type: boolean
Expand Down Expand Up @@ -1171,28 +1174,29 @@ properties:
description: |
Username with which to connect to the database. Defaults
to the username of the current user. Supports the
"!credential" tag.
"{credential ...}" syntax.
example: dbuser
restore_username:
type: string
description: |
Username with which to restore the database. Defaults to
the "username" option. Supports the "!credential" tag.
the "username" option. Supports the "{credential ...}"
syntax.
example: dbuser
password:
type: string
description: |
Password with which to connect to the database. Omitting
a password will only work if MariaDB is configured to
trust the configured username without a password.
Supports the "!credential" tag.
Supports the "{credential ...}" syntax.
example: trustsome1
restore_password:
type: string
description: |
Password with which to connect to the restore database.
Defaults to the "password" option. Supports the
"!credential" tag.
"{credential ...}" syntax.
example: trustsome1
mariadb_dump_command:
type: string
Expand Down Expand Up @@ -1300,28 +1304,29 @@ properties:
description: |
Username with which to connect to the database. Defaults
to the username of the current user. Supports the
"!credential" tag.
"{credential ...}" syntax.
example: dbuser
restore_username:
type: string
description: |
Username with which to restore the database. Defaults to
the "username" option. Supports the "!credential" tag.
the "username" option. Supports the "{credential ...}"
syntax.
example: dbuser
password:
type: string
description: |
Password with which to connect to the database. Omitting
a password will only work if MySQL is configured to
trust the configured username without a password.
Supports the "!credential" tag.
Supports the "{credential ...}" syntax.
example: trustsome1
restore_password:
type: string
description: |
Password with which to connect to the restore database.
Defaults to the "password" option. Supports the
"!credential" tag.
"{credential ...}" syntax.
example: trustsome1
mysql_dump_command:
type: string
Expand Down Expand Up @@ -1459,27 +1464,28 @@ properties:
description: |
Username with which to connect to the database. Skip it
if no authentication is needed. Supports the
"!credential" tag.
"{credential ...}" syntax.
example: dbuser
restore_username:
type: string
description: |
Username with which to restore the database. Defaults to
the "username" option. Supports the "!credential" tag.
the "username" option. Supports the "{credential ...}"
syntax.
example: dbuser
password:
type: string
description: |
Password with which to connect to the database. Skip it
if no authentication is needed. Supports the
"!credential" tag.
"{credential ...}" syntax.
example: trustsome1
restore_password:
type: string
description: |
Password with which to connect to the restore database.
Defaults to the "password" option. Supports the
"!credential" tag.
"{credential ...}" syntax.
example: trustsome1
authentication_database:
type: string
Expand Down Expand Up @@ -1539,19 +1545,19 @@ properties:
type: string
description: |
The username used for authentication. Supports the
"!credential" tag.
"{credential ...}" syntax.
example: testuser
password:
type: string
description: |
The password used for authentication. Supports the
"!credential" tag.
"{credential ...}" syntax.
example: fakepassword
access_token:
type: string
description: |
An ntfy access token to authenticate with instead of
username/password. Supports the "!credential" tag.
username/password. Supports the "{credential ...}" syntax.
example: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2
start:
type: object
Expand Down Expand Up @@ -1646,16 +1652,16 @@ properties:
token:
type: string
description: |
Your application's API token. Supports the "!credential"
tag.
Your application's API token. Supports the "{credential
...}" syntax.
example: 7ms6TXHpTokTou2P6x4SodDeentHRa
user:
type: string
description: |
Your user/group key (or that of your target user), viewable
when logged into your dashboard: often referred to as
USER_KEY in Pushover documentation and code examples.
Supports the "!credential" tag.
Supports the "{credential ...}" syntax.
example: hwRwoWsXMBWwgrSecfa9EfPey55WSN
start:
type: object
Expand Down Expand Up @@ -1929,19 +1935,19 @@ properties:
type: string
description: |
The username used for authentication. Not needed if using
an API key. Supports the "!credential" tag.
an API key. Supports the "{credential ...}" syntax.
example: testuser
password:
type: string
description: |
The password used for authentication. Not needed if using
an API key. Supports the "!credential" tag.
an API key. Supports the "{credential ...}" syntax.
example: fakepassword
api_key:
type: string
description: |
The API key used for authentication. Not needed if using
an username/password. Supports the "!credential" tag.
The API key used for authentication. Not needed if using an
username/password. Supports the "{credential ...}" syntax.
example: fakekey
start:
type: object
Expand Down Expand Up @@ -2221,8 +2227,8 @@ properties:
integration_key:
type: string
description: |
PagerDuty integration key used to notify PagerDuty
when a backup errors. Supports the "!credential" tag.
PagerDuty integration key used to notify PagerDuty when a
backup errors. Supports the "{credential ...}" syntax.
example: a177cad45bd374409f78906a810a3074
description: |
Configuration for a monitoring integration with PagerDuty. Create an
Expand Down
42 changes: 42 additions & 0 deletions borgmatic/hooks/credential/parse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import functools
import re

import borgmatic.hooks.dispatch

IS_A_HOOK = False


CREDENTIAL_PATTERN = re.compile(
r'\{credential +(?P<hook_name>[A-Za-z0-9_]+) +(?P<credential_name>[A-Za-z0-9_]+)\}'
)

GENERAL_CREDENTIAL_PATTERN = re.compile(r'\{credential( +[^}]*)?\}')


@functools.cache
def resolve_credential(value):
'''
Given a configuration value containing a string like "{credential hookname credentialname}", resolve it by
calling the relevant hook to get the actual credential value. If the given value does not
actually contain a credential tag, then return it unchanged.
Cache the value so repeated calls to this function don't need to load the credential repeatedly.
Raise ValueError if the config could not be parsed or the credential could not be loaded.
'''
if value is None:
return value

result = CREDENTIAL_PATTERN.sub(
lambda matcher: borgmatic.hooks.dispatch.call_hook(
'load_credential', {}, matcher.group('hook_name'), matcher.group('credential_name')
),
value,
)

# If we've tried to parse the credential, but the parsed result still looks kind of like a
# credential, it means it's invalid syntax.
if GENERAL_CREDENTIAL_PATTERN.match(result):
raise ValueError(f'Cannot load credential with invalid syntax "{value}"')

return result
27 changes: 0 additions & 27 deletions borgmatic/hooks/credential/tag.py

This file was deleted.

Loading

0 comments on commit 5009629

Please sign in to comment.