Merge pull request #8844 from ThomasWaldmann/write-only

repository: add write-only permissions mode, fixes #1165
This commit is contained in:
TW 2025-05-18 07:25:01 +02:00 committed by GitHub
commit 7afb649262
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 82 additions and 3 deletions

View file

@ -498,6 +498,8 @@ class Archive:
deleted=False,
):
name_is_id = isinstance(name, bytes)
if not name_is_id:
assert len(name) <= 255
self.cwd = os.getcwd()
assert isinstance(manifest, Manifest)
self.manifest = manifest

View file

@ -7,6 +7,8 @@ from collections import namedtuple
from datetime import datetime, timezone, timedelta
from time import perf_counter
from borgstore.backends.errors import PermissionDenied
from .logger import create_logger
logger = create_logger()
@ -443,7 +445,10 @@ class FilesCacheMixin:
from .archive import Archive
# get the latest archive with the IDENTICAL name, supporting archive series:
archives = self.manifest.archives.list(match=[self.archive_name], sort_by=["ts"], last=1)
try:
archives = self.manifest.archives.list(match=[self.archive_name], sort_by=["ts"], last=1)
except PermissionDenied: # maybe repo is in write-only mode?
archives = None
if not archives:
# nothing found
return

View file

@ -553,7 +553,6 @@ class Manifest:
self.timestamp = max_ts.isoformat(timespec="microseconds")
# include checks for limits as enforced by limited unpacker (used by load())
assert self.archives.count() <= MAX_ARCHIVES
assert all(len(name) <= 255 for name in self.archives.names())
assert len(self.item_keys) <= 100
self.config["item_keys"] = tuple(sorted(self.item_keys))
manifest_archives = self.archives.finish(self)

View file

@ -130,11 +130,22 @@ class Repository:
"keys": "lr",
"locks": "lrwD", # borg needs to create/delete a shared lock here
}
elif permissions == "write-only": # mostly no reading
permissions = {
"": "l",
"archives": "lw",
"cache": "lrwWD", # read allowed, e.g. for chunks.<HASH> cache
"config": "lrW", # W for manifest
"data": "lw", # no r!
"keys": "lr",
"locks": "lrwD", # borg needs to create/delete a shared lock here
}
elif permissions == "read-only": # mostly r/o
permissions = {"": "lr", "locks": "lrwD"}
else:
raise Error(
f"Invalid BORG_REPO_PERMISSIONS value: {permissions}, should be one of: all, no-delete, read-only"
f"Invalid BORG_REPO_PERMISSIONS value: {permissions}, should be one of: "
f"all, no-delete, write-only, read-only."
)
try:

View file

@ -136,3 +136,65 @@ def test_repository_permissions_read_only(archivers, request, monkeypatch):
# Try to compact the repo, which should fail.
with pytest.raises(PermissionDenied):
cmd(archiver, "compact")
def test_repository_permissions_write_only(archivers, request, monkeypatch):
"""Test repository with 'write-only' permissions setting"""
archiver = request.getfixturevalue(archivers)
# Create a repository first (need unrestricted permissions for that).
monkeypatch.setenv("BORG_REPO_PERMISSIONS", "all")
cmd(archiver, "repo-create", RK_ENCRYPTION)
# Create an initial archive to test with.
create_test_files(archiver.input_path)
cmd(archiver, "create", "archive1", "input")
# Switch to write-only permissions.
monkeypatch.setenv("BORG_REPO_PERMISSIONS", "write-only")
# Try to create a new archive, which should succeed
cmd(archiver, "create", "archive2", "input")
# Try to list archives, which should fail (requires reading from data directory).
with pytest.raises(PermissionDenied):
cmd(archiver, "repo-list")
# Try to list files in an archive, which should fail (requires reading from data directory).
with pytest.raises(PermissionDenied):
cmd(archiver, "list", "archive1")
with pytest.raises(PermissionDenied):
cmd(archiver, "list", "archive2")
# Try to extract the archive, which should fail (data dir has "lw" permissions, no reading).
with pytest.raises(PermissionDenied):
with changedir("output"):
cmd(archiver, "extract", "archive1")
# Try to delete an archive, which should fail (requires reading from data directory to identify the archive).
with pytest.raises(PermissionDenied):
cmd(archiver, "delete", "archive1")
# Try to compact the repo, which should fail (data dir has "lw" permissions, no reading).
with pytest.raises(PermissionDenied):
cmd(archiver, "compact")
# Try to check the repo, which should fail (data dir has "lw" permissions, no reading).
with pytest.raises(PermissionDenied):
cmd(archiver, "check")
# Try to delete the repo, which should fail (no "D" permission on data dir).
with pytest.raises(PermissionDenied):
cmd(archiver, "repo-delete")
# Switch to read-only permissions.
monkeypatch.setenv("BORG_REPO_PERMISSIONS", "read-only")
# Try to list archives, should work now.
output = cmd(archiver, "repo-list")
assert "archive1" in output
assert "archive2" in output
# Try to list files in an archive, should work now.
cmd(archiver, "list", "archive1")
cmd(archiver, "list", "archive2")