Merge pull request #8837 from ThomasWaldmann/store-permissions2

Store permissions
This commit is contained in:
TW 2025-05-16 13:17:03 +02:00 committed by GitHub
commit e0fd2af7bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 162 additions and 2 deletions

View file

@ -30,7 +30,7 @@ license = "BSD-3-Clause"
license-files = ["LICENSE", "AUTHORS"]
dependencies = [
"borghash ~= 0.1.0",
"borgstore ~= 0.2.0",
"borgstore @ git+https://github.com/borgbackup/borgstore.git@master", # temporary until there is a release
"msgpack >=1.0.3, <=1.1.0",
"packaging",
"platformdirs >=3.0.0, <5.0.0; sys_platform == 'darwin'", # for macOS: breaking changes in 3.0.0,

View file

@ -115,8 +115,30 @@ class Repository:
"keys/": [0],
"locks/": [0],
}
# Get permissions from environment variable
permissions = os.environ.get("BORG_REPO_PERMISSIONS", "all")
if permissions == "all":
permissions = None # permissions system will not be used
elif permissions == "no-delete": # mostly no delete, no overwrite
permissions = {
"": "lr",
"archives": "lrw",
"cache": "lrwWD", # WD for chunks.X
"config": "lrWD", # W for manifest, D for last-key-checked
"data": "lrw",
"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"
)
try:
self.store = Store(url, levels=levels_config)
self.store = Store(url, levels=levels_config, permissions=permissions)
except StoreBackendError as e:
raise Error(str(e))
self.store_opened = False

View file

@ -0,0 +1,138 @@
import os
import pytest
from borgstore.backends.errors import PermissionDenied
from ...constants import * # NOQA
from .. import changedir
from . import cmd, create_test_files, RK_ENCRYPTION, generate_archiver_tests
pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local") # NOQA
def test_repository_permissions_all(archivers, request, monkeypatch):
"""Test repository with 'all' permissions setting"""
archiver = request.getfixturevalue(archivers)
# Create a repository with unrestricted permissions.
monkeypatch.setenv("BORG_REPO_PERMISSIONS", "all")
cmd(archiver, "repo-create", RK_ENCRYPTION)
create_test_files(archiver.input_path)
cmd(archiver, "create", "archive1", "input")
# Verify the archive was created.
assert "archive1" in cmd(archiver, "repo-list")
# Delete the archive to verify unrestricted permissions.
cmd(archiver, "delete", "archive1")
# Verify the archive was deleted.
assert "archive1" not in cmd(archiver, "repo-list")
# Delete the repository to verify unrestricted permissions.
cmd(archiver, "repo-delete")
def test_repository_permissions_no_delete(archivers, request, monkeypatch):
"""Test repository with 'no-delete' permissions setting"""
archiver = request.getfixturevalue(archivers)
create_test_files(archiver.input_path)
# Create a repository first (need unrestricted permissions for that).
monkeypatch.setenv("BORG_REPO_PERMISSIONS", "all")
cmd(archiver, "repo-create", RK_ENCRYPTION)
cmd(archiver, "create", "archive1", "input")
cmd(archiver, "delete", "archive1") # this is so that compact has some chunk to remove
# Switch to no-delete permissions.
monkeypatch.setenv("BORG_REPO_PERMISSIONS", "no-delete")
# Creating new archives should work.
cmd(archiver, "create", "archive2", "input")
# Verify the archive was created.
assert "archive2" in cmd(archiver, "repo-list")
# Try to delete the archive, which should fail.
with pytest.raises(PermissionDenied):
cmd(archiver, "delete", "archive2")
# Verify the archive still exists.
assert "archive2" in cmd(archiver, "repo-list")
# Try to rename an archive, which should fail.
with pytest.raises(PermissionDenied):
cmd(archiver, "rename", "archive2", "archive3")
# Verify the archive still exists.
assert "archive2" in cmd(archiver, "repo-list")
# Try to delete the repo, which should fail.
with pytest.raises(PermissionDenied):
cmd(archiver, "repo-delete")
# Verify the archive still exists.
assert "archive2" in cmd(archiver, "repo-list")
# Try to compact the repo, which should fail.
with pytest.raises(PermissionDenied):
cmd(archiver, "compact")
# Check without --repair should work.
cmd(archiver, "check")
# Try to check --repair, which should fail.
with pytest.raises(PermissionDenied):
cmd(archiver, "check", "--repair")
# Try to repo-compress (and change compression from lz4 to zstd), which should fail.
# It fails because it needs to overwrite existing chunks, which is also disallowed by no-delete.
with pytest.raises(PermissionDenied):
cmd(archiver, "repo-compress", "-C", "zstd")
def test_repository_permissions_read_only(archivers, request, monkeypatch):
"""Test repository with 'read-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 archive to test with.
create_test_files(archiver.input_path)
cmd(archiver, "create", "archive2", "input")
# Switch to read-only permissions.
monkeypatch.setenv("BORG_REPO_PERMISSIONS", "read-only")
# Verify we can list archives.
assert "archive2" in cmd(archiver, "repo-list")
# Verify we can list files in an archive.
assert "input/" in cmd(archiver, "list", "archive2")
# Extract the archive.
with changedir("output"):
cmd(archiver, "extract", "archive2")
# Verify extraction worked.
extracted_files = os.listdir("output")
assert len(extracted_files) > 0
# Try to create a new archive, which should fail.
with pytest.raises(PermissionDenied):
cmd(archiver, "create", "archive3", "input")
# Try to delete an archive, which should fail.
with pytest.raises(PermissionDenied):
cmd(archiver, "delete", "archive2")
# Try to delete the repo, which should fail.
with pytest.raises(PermissionDenied):
cmd(archiver, "repo-delete")
# Try to compact the repo, which should fail.
with pytest.raises(PermissionDenied):
cmd(archiver, "compact")