Seal catalog publication Git metadata
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
@@ -26,12 +27,65 @@ from govoplan_release.publisher import ( # noqa: E402
|
||||
verify_committed_publication,
|
||||
verify_remote_branch_head,
|
||||
verify_remote_publication,
|
||||
_git_bytes,
|
||||
_sanitized_git_environment,
|
||||
_seal_git_metadata_file,
|
||||
_trusted_npm_command,
|
||||
)
|
||||
|
||||
|
||||
class ReleaseCatalogPublicationTests(unittest.TestCase):
|
||||
def test_publication_git_writes_ignore_permissive_operator_umask(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
repository = Path(temp_dir) / "website"
|
||||
repository.mkdir()
|
||||
self._git(repository, "init", "--quiet")
|
||||
payload_number = 0
|
||||
while True:
|
||||
payload = f"private publication object {payload_number}\n".encode()
|
||||
header = f"blob {len(payload)}\0".encode()
|
||||
expected_object = hashlib.sha1(
|
||||
header + payload,
|
||||
usedforsecurity=False,
|
||||
).hexdigest()
|
||||
object_parent = repository / ".git" / "objects" / expected_object[:2]
|
||||
if not object_parent.exists():
|
||||
break
|
||||
payload_number += 1
|
||||
|
||||
previous_umask = os.umask(0o002)
|
||||
try:
|
||||
object_id = (
|
||||
_git_bytes(
|
||||
repository,
|
||||
"hash-object",
|
||||
"-w",
|
||||
"--stdin",
|
||||
input_bytes=payload,
|
||||
)
|
||||
.decode("ascii")
|
||||
.strip()
|
||||
)
|
||||
finally:
|
||||
os.umask(previous_umask)
|
||||
|
||||
object_path = object_parent / expected_object[2:]
|
||||
self.assertEqual(expected_object, object_id)
|
||||
self.assertEqual(os.geteuid(), object_parent.stat().st_uid)
|
||||
self.assertEqual(0o700, object_parent.stat().st_mode & 0o777)
|
||||
self.assertEqual(os.geteuid(), object_path.stat().st_uid)
|
||||
self.assertEqual(0o400, object_path.stat().st_mode & 0o777)
|
||||
|
||||
def test_publication_git_metadata_is_sealed_after_git_writes(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
metadata = Path(temp_dir) / "index"
|
||||
metadata.write_bytes(b"index")
|
||||
metadata.chmod(0o664)
|
||||
|
||||
_seal_git_metadata_file(metadata, label="test index")
|
||||
|
||||
self.assertEqual(0o600, metadata.stat().st_mode & 0o777)
|
||||
|
||||
def test_publication_git_identity_is_fixed_and_non_personal(self) -> None:
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
|
||||
@@ -614,6 +614,14 @@ def publish_catalog_candidate(
|
||||
effective_tag_name,
|
||||
publication_commit,
|
||||
)
|
||||
_seal_git_metadata_file(
|
||||
resolved_web_root
|
||||
/ ".git"
|
||||
/ "refs"
|
||||
/ "tags"
|
||||
/ effective_tag_name,
|
||||
label="website publication tag reference",
|
||||
)
|
||||
publication_tag_object = git_text(
|
||||
resolved_web_root,
|
||||
"rev-parse",
|
||||
@@ -1445,9 +1453,38 @@ def commit_publication_tree(
|
||||
frozen_head,
|
||||
)
|
||||
_git_checked(web_root, "read-tree", "--reset", commit_sha)
|
||||
_seal_git_metadata_file(
|
||||
web_root / ".git" / "refs" / "heads" / branch,
|
||||
label="website branch reference",
|
||||
)
|
||||
_seal_git_metadata_file(
|
||||
web_root / ".git" / "index",
|
||||
label="website Git index",
|
||||
)
|
||||
return commit_sha
|
||||
|
||||
|
||||
def _seal_git_metadata_file(path: Path, *, label: str) -> None:
|
||||
try:
|
||||
metadata = path.lstat()
|
||||
if (
|
||||
stat.S_ISLNK(metadata.st_mode)
|
||||
or not stat.S_ISREG(metadata.st_mode)
|
||||
or metadata.st_uid != os.geteuid()
|
||||
):
|
||||
raise RuntimeError(f"{label} is not an operator-owned regular file")
|
||||
os.chmod(path, 0o600, follow_symlinks=False)
|
||||
sealed = path.lstat()
|
||||
except OSError as exc:
|
||||
raise RuntimeError(f"{label} cannot be sealed") from exc
|
||||
if (
|
||||
not stat.S_ISREG(sealed.st_mode)
|
||||
or sealed.st_uid != os.geteuid()
|
||||
or stat.S_IMODE(sealed.st_mode) != 0o600
|
||||
):
|
||||
raise RuntimeError(f"{label} was not sealed to operator-only access")
|
||||
|
||||
|
||||
def verify_committed_publication(
|
||||
*,
|
||||
web_root: Path,
|
||||
@@ -1948,6 +1985,8 @@ def _git_bytes(
|
||||
"-C",
|
||||
str(path),
|
||||
"-c",
|
||||
"core.sharedRepository=0600",
|
||||
"-c",
|
||||
"core.fsmonitor=false",
|
||||
"-c",
|
||||
"core.hooksPath=/dev/null",
|
||||
|
||||
Reference in New Issue
Block a user