Skip to content

Commit 800f97f

Browse files
cleanups
1 parent 56b9542 commit 800f97f

7 files changed

Lines changed: 29 additions & 53 deletions

File tree

docs/changes.rst

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -168,20 +168,7 @@ above.
168168

169169
New features:
170170

171-
- monitoring: ``borg create`` and ``borg prune`` now append a signed-and-encrypted state
172-
report into the repository's ``monitoring/`` namespace (append-only, one object per run,
173-
named by publish time). The new ``borg monitor`` command reads, verifies and decrypts
174-
the reports from the (untrusted) repository server without the repository passphrase and
175-
prints, per archive series and per maintenance command, the latest status and freshness
176-
- so a later successful backup of one series cannot mask an earlier failed backup of
177-
another. Restrict with ``--name`` (one series) or ``--command``. It exits non-zero if any
178-
unit is missing, stale (older than ``--max-age``), unsigned or unsuccessful - so it can
179-
drive alerting like a dead man's switch. ``--keep=N`` (default 500) deletes all but the N
180-
newest report objects after reading (needs delete permission on the monitoring
181-
namespace). All key material is derived from the existing borg key; run ``borg monitor
182-
--key`` once on a host that has the key to obtain ``BORG_MONITORING_KEY`` for the
183-
monitoring host. Reports are signed with Ed25519 and sealed with HPKE (RFC 9180), so the
184-
server can neither forge nor read them, only relay them. Requires OpenSSL >= 3.2.
171+
- monitor: access encrypted/signed monitoring data in the repository, #9788
185172
- repo-create: split ``--encryption`` into orthogonal options. ``--encryption`` now
186173
selects only the cipher / AE algorithm (``none``, ``authenticated``, ``aes256-ocb``
187174
or ``chacha20-poly1305``), the new ``--id-hash`` selects the id hash function

docs/usage/general/environment.rst.inc

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -257,11 +257,12 @@ Directories and files:
257257
- you need to give a different path for different repositories.
258258
- you need to point to the correct key file matching the repository the command will operate on.
259259
BORG_MONITORING_KEY
260-
Used by ``borg monitor`` (without ``--key``) on the monitoring host to verify and
261-
decrypt the report a backup client published into the repository. Obtain its value
262-
once, on a host that has the borg key, via ``borg monitor --key``. It only allows
263-
verifying and decrypting reports, not creating them, and it does not grant access
264-
to the backup data.
260+
Used by ``borg monitor`` on the monitoring host to verify and decrypt
261+
the reports backup clients published into the repository.
262+
You can get the correct key value by running ``borg monitor --key`` on
263+
a host that has access to the borg key.
264+
This key can not be used to create reports, nor does it grant access
265+
to backup data.
265266
TMPDIR
266267
This is where temporary files are stored (might need a lot of temporary space for some
267268
operations), see tempfile_ for details.

src/borg/archiver/create_cmd.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from ._common import with_repository, Highlander
1212
from .. import helpers
13+
from .. import monitoring
1314
from ..archive import Archive, is_special, SF_DATALESS
1415
from ..archive import BackupError, BackupOSError, BackupItemExcluded, backup_io, OsOpen, stat_update_check
1516
from ..archive import FilesystemObjectProcessors, MetadataCollector, ChunksProcessor
@@ -287,10 +288,6 @@ def create_inner(archive, cache, fso):
287288
files_changed=args.files_changed,
288289
)
289290
create_inner(archive, cache, fso)
290-
# Publish the monitoring report as the last action while the store is
291-
# still open. The RC is only best-known here (a failure after this point
292-
# won't be reflected); see borg/monitoring.py.
293-
from .. import monitoring
294291

295292
monitoring.publish_command_report(
296293
repository,
@@ -300,7 +297,7 @@ def create_inner(archive, cache, fso):
300297
archive_id=archive.id,
301298
stats=archive.stats.as_dict(),
302299
)
303-
else:
300+
else: # dry-run
304301
create_inner(None, None, None)
305302

306303
def _process_any(self, *, path, parent_fd, name, st, fso, cache, read_special, dry_run, strip_prefix):

src/borg/archiver/monitor_cmd.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,9 @@ def do_monitor(self, args, repository):
3434
series) or --command (e.g. create or prune). Neither the repository passphrase nor
3535
the borg key is needed for reading.
3636
37-
Reports accumulate as append-only objects; --keep=N deletes all but the N newest
38-
after reading (this needs delete permission on the monitoring namespace).
37+
Reports accumulate over time; --keep=N deletes all but the N newest after reading.
3938
40-
With --key (which does need the borg key) it instead derives and prints the
39+
With --key (which does need the borg key), it derives and prints the
4140
BORG_MONITORING_KEY value for this repository, to be configured on the monitoring
4241
host. The printed value only allows verifying and decrypting reports, not creating
4342
them.
@@ -138,21 +137,19 @@ def build_parser_monitor(self, subparsers, common_parser, mid_common_parser):
138137

139138
monitor_epilog = process_epilog(
140139
"""
141-
Read or export trusted monitoring state of a repository.
140+
Read trusted monitoring state of a repository.
142141
143-
Backup-side commands publish a small signed-and-encrypted state report into the
144-
repository after each run. Because each report is signed with a key derived from
145-
the borg key, the (untrusted) repository server can neither forge nor read it - it
146-
can only relay it. A monitoring system can therefore pull and verify the reports
147-
from the same server without the repository passphrase.
142+
Borg client commands publish a signed-and-encrypted state report into the
143+
repository after each run. Only borg monitor can read these reports using
144+
the monitoring key.
148145
149146
Setup (once, on a host that has the borg key)::
150147
151-
BORG_MONITORING_KEY=$(borg monitor --key)
148+
borg monitor --key # this outputs the monitoring key
152149
153-
Then, on the monitoring host, with that value exported as BORG_MONITORING_KEY::
150+
Then, on the monitoring host::
154151
155-
borg monitor
152+
BORG_MONITORING_KEY=<that key> borg monitor
156153
157154
This verifies and decrypts the reports and prints, per archive series (and per
158155
maintenance command), the latest status and its age. It exits with a non-zero code
@@ -199,6 +196,6 @@ def build_parser_monitor(self, subparsers, common_parser, mid_common_parser):
199196
default=monitoring.DEFAULT_KEEP,
200197
metavar="N",
201198
help="after reading, delete all but the N newest report objects "
202-
f"(needs delete permission; 0 = do not clean up; default: {monitoring.DEFAULT_KEEP})",
199+
f"(0 = do not clean up; default: {monitoring.DEFAULT_KEEP})",
203200
)
204201
subparser.add_argument("--json", action="store_true", help="format output as JSON")

src/borg/archiver/prune_cmd.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from operator import attrgetter
55
import os
66

7+
from .. import monitoring
78
from ._common import with_repository, Highlander
89
from ..constants import * # NOQA
910
from ..helpers import ArchiveFormatter, interval, sig_int, ProgressIndicatorPercent, CommandError, Error
@@ -233,9 +234,6 @@ def do_prune(self, args, repository, manifest):
233234
raise Error("Got Ctrl-C / SIGINT.")
234235

235236
if not args.dry_run:
236-
# Publish the monitoring report as the last action while the store is open.
237-
from .. import monitoring
238-
239237
monitoring.publish_command_report(
240238
repository,
241239
manifest.key,

src/borg/crypto/monitoring.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ def _derive_seed(key, domain):
5050
def client_material(key):
5151
"""Client half: (ed25519_sign_seed, hpke_recipient_public).
5252
53-
Used by the publishing side to sign with the Ed25519 secret seed and seal to the
54-
monitor's HPKE public key.
53+
Used by the publishing side to sign with the Ed25519 secret seed and seal
54+
to the monitor's HPKE public key.
5555
"""
5656
sign_seed = _derive_seed(key, SIGN_DOMAIN)
5757
seal_seed = _derive_seed(key, SEAL_DOMAIN)
@@ -62,8 +62,8 @@ def client_material(key):
6262
def monitor_material(key):
6363
"""Monitor half: (ed25519_verify_public, hpke_recipient_secret).
6464
65-
This is everything the monitoring system needs to verify + decrypt and nothing more:
66-
it cannot derive the signing secret or the borg key from it.
65+
This is everything the monitoring system needs to verify and decrypt and
66+
nothing more: it cannot derive the signing secret or the borg key from it.
6767
"""
6868
sign_seed = _derive_seed(key, SIGN_DOMAIN)
6969
seal_seed = _derive_seed(key, SEAL_DOMAIN)

src/borg/monitoring.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,14 @@
2323
import time
2424
from binascii import hexlify
2525

26+
from borgstore.store import ItemInfo
27+
from borgstore.store import ObjectNotFound as StoreObjectNotFound
28+
2629
from . import __version__
2730
from .constants import EXIT_SUCCESS, EXIT_WARNING, EXIT_WARNING_BASE, EXIT_SIGNAL_BASE
2831
from .crypto import monitoring as mon_crypto
32+
from .helpers import bin_to_hex, get_ec
33+
from .helpers.time import archive_ts_now
2934

3035
logger = logging.getLogger(__name__)
3136

@@ -135,9 +140,6 @@ def publish_command_report(repository, key, command, *, archive=None, archive_id
135140
after the store is closed; see borg/monitoring.py). Call this as the last action while
136141
the store is still open. *archive_id* is binary; it is hex-encoded for the report.
137142
"""
138-
from .helpers import bin_to_hex, get_ec
139-
from .helpers.time import archive_ts_now
140-
141143
report = build_report(
142144
command=command,
143145
repo_id=bin_to_hex(repository.id),
@@ -152,8 +154,6 @@ def publish_command_report(repository, key, command, *, archive=None, archive_id
152154

153155
def list_names(repository):
154156
"""Return all monitoring object names, oldest first (names sort chronologically)."""
155-
from borgstore.store import ItemInfo
156-
157157
names = [ItemInfo(*info).name for info in repository.store_list(STORE_NAMESPACE)]
158158
names.sort()
159159
return names
@@ -165,8 +165,6 @@ def iter_reports(repository, monitor_key):
165165
Each report is verified and decrypted; an unverifiable one raises (it is not silently
166166
skipped) so tampering surfaces.
167167
"""
168-
from borgstore.store import ObjectNotFound as StoreObjectNotFound
169-
170168
for name in list_names(repository):
171169
try:
172170
data = repository.store_load(f"{STORE_NAMESPACE}/{name}")
@@ -181,8 +179,6 @@ def prune_reports(repository, keep):
181179
Needs delete permission on the monitoring namespace; on a permission error (e.g. a
182180
read-only monitoring host) it warns and stops rather than failing the command.
183181
"""
184-
from borgstore.store import ObjectNotFound as StoreObjectNotFound
185-
186182
if keep is None or keep <= 0:
187183
return 0 # 0 (or negative) disables cleanup
188184
names = list_names(repository)

0 commit comments

Comments
 (0)