mirror of
https://github.com/ArchiveBox/ArchiveBox.git
synced 2026-09-14 11:06:13 +05:00
Allow root-home collections after privilege drop
This commit is contained in:
parent
f264c22d5d
commit
c3aa7762cd
@ -4,6 +4,7 @@ import os
|
||||
import platform
|
||||
import pwd
|
||||
import socket
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
from contextlib import contextmanager, suppress
|
||||
@ -164,6 +165,8 @@ ROOT_HANDOFF_NAMES = (
|
||||
"index.sqlite3-wal",
|
||||
"archive",
|
||||
"cache",
|
||||
"custom_plugins",
|
||||
"custom_templates",
|
||||
"lib",
|
||||
"logs",
|
||||
"personas",
|
||||
@ -208,6 +211,16 @@ def root_should_handoff_data_dir(
|
||||
return is_root and account_uid is not None and (not data_dir_owner_exists or data_dir_uid in (0, account_uid))
|
||||
|
||||
|
||||
def root_parent_can_grant_group_traversal(*, parent_uid: int, parent_gid: int, parent_mode: int, account_gid: int) -> bool:
|
||||
"""Return whether adding archivebox traversal preserves existing parent access."""
|
||||
|
||||
if parent_uid != 0 or parent_mode & stat.S_IXOTH:
|
||||
return False
|
||||
if parent_gid == account_gid:
|
||||
return not bool(parent_mode & stat.S_IXGRP)
|
||||
return not bool(parent_mode & stat.S_IRWXG)
|
||||
|
||||
|
||||
def handoff_root_owned_data_dir() -> None:
|
||||
account_uid = ARCHIVEBOX_ACCOUNT.pw_uid if ARCHIVEBOX_ACCOUNT is not None else None
|
||||
if not root_should_handoff_data_dir(
|
||||
@ -218,7 +231,27 @@ def handoff_root_owned_data_dir() -> None:
|
||||
):
|
||||
return
|
||||
|
||||
for path in root_data_dir_handoff_paths(DATA_DIR, sys.argv):
|
||||
handoff_paths = root_data_dir_handoff_paths(DATA_DIR, sys.argv)
|
||||
if not handoff_paths:
|
||||
return
|
||||
|
||||
# A root user's collection commonly lives below /root, which the archivebox
|
||||
# account cannot traverse after EUID is dropped. Grant only that group the
|
||||
# missing execute bit on root-owned parents; never walk collection contents.
|
||||
for parent in DATA_DIR.resolve().parents:
|
||||
if parent == Path("/"):
|
||||
continue
|
||||
parent_stat = parent.stat(follow_symlinks=False)
|
||||
if root_parent_can_grant_group_traversal(
|
||||
parent_uid=parent_stat.st_uid,
|
||||
parent_gid=parent_stat.st_gid,
|
||||
parent_mode=parent_stat.st_mode,
|
||||
account_gid=ARCHIVEBOX_ACCOUNT.pw_gid,
|
||||
):
|
||||
os.chown(parent, -1, ARCHIVEBOX_ACCOUNT.pw_gid, follow_symlinks=False)
|
||||
os.chmod(parent, stat.S_IMODE(parent_stat.st_mode) | stat.S_IXGRP, follow_symlinks=False)
|
||||
|
||||
for path in handoff_paths:
|
||||
try:
|
||||
os.chown(path, ARCHIVEBOX_ACCOUNT.pw_uid, ARCHIVEBOX_ACCOUNT.pw_gid, follow_symlinks=False)
|
||||
except (FileNotFoundError, PermissionError):
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
from pathlib import Path
|
||||
|
||||
from archivebox.config.permissions import is_root_identity, root_should_handoff_data_dir, select_archivebox_user
|
||||
from archivebox.config.permissions import (
|
||||
is_root_identity,
|
||||
root_parent_can_grant_group_traversal,
|
||||
root_should_handoff_data_dir,
|
||||
select_archivebox_user,
|
||||
)
|
||||
|
||||
|
||||
def test_root_identity_includes_real_or_effective_root():
|
||||
@ -115,16 +120,20 @@ def test_existing_collection_handoff_is_bounded_to_known_top_level_paths(tmp_pat
|
||||
|
||||
database = tmp_path / "index.sqlite3"
|
||||
archive = tmp_path / "archive"
|
||||
custom_plugins = tmp_path / "custom_plugins"
|
||||
custom_templates = tmp_path / "custom_templates"
|
||||
nested = archive / "large-existing-snapshot"
|
||||
errors_log = tmp_path / "logs" / "errors.log"
|
||||
database.touch()
|
||||
nested.mkdir(parents=True)
|
||||
custom_plugins.mkdir()
|
||||
custom_templates.mkdir()
|
||||
errors_log.parent.mkdir()
|
||||
errors_log.touch()
|
||||
|
||||
paths = root_data_dir_handoff_paths(tmp_path, ["archivebox", "status"])
|
||||
|
||||
assert paths == (tmp_path, database, archive, errors_log.parent, errors_log)
|
||||
assert paths == (tmp_path, database, archive, custom_plugins, custom_templates, errors_log.parent, errors_log)
|
||||
assert nested not in paths
|
||||
assert all(path == tmp_path or path.parent in (tmp_path, errors_log.parent) for path in paths)
|
||||
|
||||
@ -139,3 +148,11 @@ def test_root_handoff_never_selects_filesystem_root():
|
||||
from archivebox.config.permissions import root_data_dir_handoff_paths
|
||||
|
||||
assert root_data_dir_handoff_paths(Path("/"), ["archivebox", "init"]) == ()
|
||||
|
||||
|
||||
def test_root_private_parent_grants_only_archivebox_group_traversal():
|
||||
assert root_parent_can_grant_group_traversal(parent_uid=0, parent_gid=0, parent_mode=0o700, account_gid=911)
|
||||
assert not root_parent_can_grant_group_traversal(parent_uid=0, parent_gid=0, parent_mode=0o701, account_gid=911)
|
||||
assert not root_parent_can_grant_group_traversal(parent_uid=0, parent_gid=911, parent_mode=0o710, account_gid=911)
|
||||
assert not root_parent_can_grant_group_traversal(parent_uid=0, parent_gid=100, parent_mode=0o750, account_gid=911)
|
||||
assert not root_parent_can_grant_group_traversal(parent_uid=1000, parent_gid=1000, parent_mode=0o700, account_gid=911)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user