Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/borg/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,8 @@ def __init__(
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
Expand Down
7 changes: 6 additions & 1 deletion src/borg/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
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()
Expand Down Expand Up @@ -443,7 +445,10 @@ def _build_files_cache(self):
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
Expand Down
1 change: 0 additions & 1 deletion src/borg/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,6 @@ def write(self):
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)
Expand Down
13 changes: 12 additions & 1 deletion src/borg/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,22 @@ def __init__(self, path_or_location, create=False, exclusive=False, lock_wait=1.
"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:
Expand Down
62 changes: 62 additions & 0 deletions src/borg/testsuite/archiver/restricted_permissions_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Loading