2
0
Эх сурвалжийг харах

Merge pull request #8837 from ThomasWaldmann/store-permissions2

Store permissions
TW 3 долоо хоног өмнө
parent
commit
e0fd2af7bd

+ 1 - 1
pyproject.toml

@@ -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,

+ 23 - 1
src/borg/repository.py

@@ -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

+ 138 - 0
src/borg/testsuite/archiver/restricted_permissions_test.py

@@ -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")