fix(hlcc): persist exact repository ACL sharing

This commit is contained in:
冰朔 2026-08-06 16:28:56 +08:00
commit ba6aca7f24
5 changed files with 173 additions and 27 deletions

View file

@ -18,6 +18,7 @@ import time
import urllib.error
import urllib.parse
import urllib.request
from collections.abc import Callable
VERSION = "16.0.1"
@ -482,39 +483,68 @@ def seed_fifth_domain_channel(binary: pathlib.Path) -> str:
def configure_shared_channel_repository(
repository: pathlib.Path = CHANNEL_REPOSITORY_PATH,
generated_hook: pathlib.Path | None = None,
acl_runner: Callable[[list[str]], None] | None = None,
) -> str:
"""Use Git's native shared-repository mode instead of a post-receive chmod."""
"""Share only this repository through exact inherited ACLs."""
if not repository.is_dir():
raise RuntimeError("channel repository path unavailable")
subprocess.run(
unset_result = subprocess.run(
[
"git",
"--git-dir",
str(repository),
"config",
"--unset-all",
"core.sharedRepository",
"group",
],
check=True,
capture_output=True,
text=True,
)
configured = subprocess.run(
if unset_result.returncode not in {0, 5}:
raise RuntimeError("shared repository configuration removal failed")
def apply_acl(command: list[str]) -> None:
if acl_runner:
acl_runner(command)
return
subprocess.run(command, check=True, capture_output=True, text=True)
objects = repository / "objects"
refs = repository / "refs"
apply_acl(
[
"git",
"--git-dir",
str(repository),
"config",
"--get",
"core.sharedRepository",
],
check=True,
capture_output=True,
text=True,
).stdout.strip()
if configured not in {"1", "group"}:
raise RuntimeError("shared repository configuration verification failed")
"/usr/bin/setfacl",
"-R",
"-m",
"u:guanghu-authz:rwX,m::rwx",
str(objects),
str(refs),
]
)
for root in (objects, refs):
for directory, child_directories, _files in os.walk(root):
apply_acl(
[
"/usr/bin/setfacl",
"-m",
"d:u:guanghu-authz:rwx,d:m::rwx",
directory,
]
)
child_directories.sort()
for name, permissions in (("HEAD", "rw"), ("packed-refs", "rw"), ("config", "r")):
path = repository / name
if path.is_file():
apply_acl(
[
"/usr/bin/setfacl",
"-m",
f"u:guanghu-authz:{permissions}",
str(path),
]
)
obsolete_hook = (
repository
@ -530,6 +560,23 @@ def configure_shared_channel_repository(
raise RuntimeError("obsolete sharing hook identity mismatch")
obsolete_hook.unlink()
generated_hook = generated_hook or (
STATE_ROOT / "data" / "data" / "home" / "hooks" / "post-receive"
)
if not generated_hook.is_file() or generated_hook.is_symlink():
raise RuntimeError("generated post-receive hook path unavailable")
generated_text = generated_hook.read_text(encoding="utf-8")
unsafe_test = 'if [ $(basename "${hook}") != "gitea" ]; then'
safe_test = 'if [ "$(basename "${hook}")" != "gitea" ]; then'
if unsafe_test in generated_text:
generated_hook.write_text(
generated_text.replace(unsafe_test, safe_test, 1),
encoding="utf-8",
)
generated_hook.chmod(0o700)
elif safe_test not in generated_text:
raise RuntimeError("generated post-receive hook identity mismatch")
return "configured"

View file

@ -48,8 +48,12 @@ assert.match(bootstrap, /access_tokens_migrated": False/);
assert.match(bootstrap, /repositories_migrated": False/);
assert.match(bootstrap, /delete from access_token/);
assert.match(bootstrap, /core\.sharedRepository/);
assert.match(bootstrap, /"group"/);
assert.match(bootstrap, /"--unset-all"/);
assert.match(bootstrap, /\/usr\/bin\/setfacl/);
assert.match(bootstrap, /d:u:guanghu-authz:rwx/);
assert.match(bootstrap, /obsolete sharing hook identity mismatch/);
assert.match(bootstrap, /generated post-receive hook identity mismatch/);
assert.match(bootstrap, /basename "\$\{hook\}"/);
assert.match(bootstrap, /configure_shared_channel_repository\(\)/);
assert.doesNotMatch(
bootstrap,

View file

@ -150,9 +150,19 @@ class SharedRepositoryTests(unittest.TestCase):
"#!/bin/sh\nprintf '%s\\n' post_receive_permissions_reconciled\n",
encoding="utf-8",
)
generated_hook = pathlib.Path(temporary) / "generated-post-receive"
generated_hook.write_text(
'#!/usr/bin/env bash\nif [ $(basename "${hook}") != "gitea" ]; then\n :\nfi\n',
encoding="utf-8",
)
acl_commands = []
self.assertEqual(
MODULE.configure_shared_channel_repository(repository),
MODULE.configure_shared_channel_repository(
repository,
generated_hook,
acl_commands.append,
),
"configured",
)
configured = subprocess.run(
@ -164,12 +174,22 @@ class SharedRepositoryTests(unittest.TestCase):
"--get",
"core.sharedRepository",
],
check=True,
capture_output=True,
text=True,
).stdout.strip()
self.assertIn(configured, {"1", "group"})
)
self.assertNotEqual(configured.returncode, 0)
self.assertTrue(
any(
"d:u:guanghu-authz:rwx" in argument
for command in acl_commands
for argument in command
)
)
self.assertFalse(hook.exists())
self.assertIn(
'if [ "$(basename "${hook}")" != "gitea" ]; then',
generated_hook.read_text(encoding="utf-8"),
)
def test_refuses_unknown_hook_content(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
@ -178,9 +198,18 @@ class SharedRepositoryTests(unittest.TestCase):
hook = repository / "hooks" / "post-receive.d" / "guanghu-ice-heart-share"
hook.parent.mkdir(parents=True)
hook.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
generated_hook = pathlib.Path(temporary) / "generated-post-receive"
generated_hook.write_text(
'#!/usr/bin/env bash\nif [ $(basename "${hook}") != "gitea" ]; then\n :\nfi\n',
encoding="utf-8",
)
with self.assertRaisesRegex(RuntimeError, "identity mismatch"):
MODULE.configure_shared_channel_repository(repository)
MODULE.configure_shared_channel_repository(
repository,
generated_hook,
lambda _command: None,
)
self.assertTrue(hook.exists())

View file

@ -125,9 +125,11 @@ node server-tools/lake-lamp-authz/authorize-repo-push.js \
`safe.directory`,不得使用通配符。
光湖代码频道本身保留 `UMask=0077`,以免放宽数据库和其他状态目录。对需要由直达
接收器共同读写的精确裸仓库设置 Git 原生 `core.sharedRepository=group`,让新建的
`objects``refs` 从写入时就继承专用共享组权限。不要安装推送后递归 `chmod`
钩子,也不要为了共享一个裸仓库而修改整个代码频道服务的 UMask。
接收器共同读写的精确裸仓库不设置 `core.sharedRepository`,而只在该仓库的
`objects``refs` 上为 `guanghu-authz` 设置继承 ACL。这样新对象和多级分支目录
从创建时就允许精确接收账户访问,不要求安全沙箱禁止的 setgid 操作,也不扩大到代码
频道数据库或其他状态。不要安装推送后递归 `chmod` 的钩子,也不要为了共享一个裸仓库
而修改整个代码频道服务的 UMask。
部署后用当前主分支生成无变化验收 bundle`receiveBundle` 完整执行一次;验收前后
主分支 SHA 必须一致,并且代码频道账户与授权服务账户执行 `git fsck` 均通过。