Files
codex-app-server/cloud/tests/test_chv_muse_p3.py
T
host 2d12179b9a fix(chv-muse-r11fix): authd 层 1 叶子树补 errors.py + stub __init__、层 2 seed 补三键
层 1:真实的 cloud/chv/__init__.py import executor/orphans,拷进
authd 最小叶子树(/usr/local/lib/chv-authd)会让 chv-authd 在
import 时 ModuleNotFoundError 而 crash loop——先是 errors.py
缺失,补上后又在 executor 上失败(探针 fixprobe3)。新增
chv-guest/authd_init_stub.py(仅导出 ChvFailure),build.sh 拷
errors.py + stub 作叶子树的 __init__.py,不再拷真实包 __init__;
P3_LIBS_PRESENT 条件加 errors.py;EXTRA_CONTENTS 加 errors.py 与
__init__.py;两 lockfile 脚本的 DECLARED_SOURCES 同款登记。
层 2:_seed_env_entries 把 guest-env 里的
GITEA_URL/GITEA_USER/SYNC_REPO 抄进 authd-seed(build_server
要求四键齐全,只 GITEA_TOKEN 即 ValueError);没有不补、不编
默认值。测试:seed 三键落盘/缺键不补、干跑夹具含新文件、lockfile
合成清单含新两项。
2026-11-07 03:56:24 +00:00

719 lines
30 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""对齐 Muse P3 测试:authd 替身凭证 + Sentinel 式出口审批。
只用标准库与 mock,绝不启动真实 VM、不联网、不含真实 token
(测试值全是假值)。覆盖:
- authd 保险库:白名单入库、作用域绑定、单次释放、替身过期、
forget 吊销、repr/审计里绝无 token 值;
- 铸 token / 吊销(假 HTTP 客户端);
- Sentinel:SSRF 挡截、deny/ask 路径、L7 路径规则、严格能力
(绑定/过期/次数)、待审批队列;
- seed 拆分:split_authd_env、write_seed 拒收 guest-env 里的
GITEA_TOKEN、authd-seed 0600;
- wake 预审:allow/ask/deny、无同步跳过;
- 客内 authd 守护进程(真 socket + 真 seed):放凭证、拒绝
路径/主机/仓库、审计无值;
- git credential helper 端到端:真 git credential fill + 假
authd socket(守护进程真跑在测试进程内)。
"""
import importlib.util
import json
import os
import socket
import subprocess
import sys
import tempfile
import threading
import unittest
from pathlib import Path
try:
from cloud.chv.authd import (
AuthdRefusal,
CredentialVault,
mint_gitea_token,
revoke_gitea_token,
surrogate_fingerprint,
)
from cloud.chv.gitea_sync import split_authd_env
from cloud.chv.hostkeys import AUTHD_SEED_PATH, write_seed
from cloud.chv.sentinel import (
ApprovalQueue,
EgressRequest,
SentinelPolicy,
)
from cloud.sandbox import ChvFailure, CloudHypervisorExecutor
except ImportError:
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from cloud.chv.authd import (
AuthdRefusal,
CredentialVault,
mint_gitea_token,
revoke_gitea_token,
surrogate_fingerprint,
)
from cloud.chv.gitea_sync import split_authd_env
from cloud.chv.hostkeys import AUTHD_SEED_PATH, write_seed
from cloud.chv.sentinel import (
ApprovalQueue,
EgressRequest,
SentinelPolicy,
)
from cloud.sandbox import ChvFailure, CloudHypervisorExecutor
REPO_ROOT = Path(__file__).resolve().parents[2]
TOKEN = "fake-token-123"
PUBLIC_IP = "93.184.216.34"
FAKE_SYNC = {
"GITEA_URL": "https://gitea.tose.top",
"GITEA_USER": "admin",
"GITEA_TOKEN": TOKEN,
"SYNC_REPO": "admin/chv-sync-test",
"SYNC_BRANCH": "main",
"GITEA_EMAIL": "admin@tose.top",
}
def _public_resolver(host, port):
return [PUBLIC_IP]
def _load_guest_authd():
"""chv-guest/authd.py(守护进程)按文件加载,不走包名。"""
path = REPO_ROOT / "chv-guest" / "authd.py"
spec = importlib.util.spec_from_file_location(
"chv_guest_authd", path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
# -- (1) authd 保险库 ----------------------------------------------------
class VaultTest(unittest.TestCase):
def setUp(self):
self.now = [1000.0]
self.vault = CredentialVault(clock=lambda: self.now[0])
def _stored(self):
self.vault.store("gitea", "GITEA_TOKEN", TOKEN,
host="gitea.tose.top",
repo="admin/chv-sync-test")
def test_store_rejects_unknown_connector_key_empty(self):
with self.assertRaises(AuthdRefusal) as ctx:
self.vault.store("github", "GITEA_TOKEN", TOKEN)
self.assertEqual(ctx.exception.kind, "unknown_connector")
with self.assertRaises(AuthdRefusal) as ctx:
self.vault.store("gitea", "OTHER_KEY", TOKEN)
self.assertEqual(ctx.exception.kind,
"credential_not_allowlisted")
with self.assertRaises(AuthdRefusal) as ctx:
self.vault.store("gitea", "GITEA_TOKEN", " ")
self.assertEqual(ctx.exception.kind, "empty_credential")
self.assertFalse(self.vault.has("gitea", "GITEA_TOKEN"))
def test_release_single_use_and_scope_binding(self):
self._stored()
rel = self.vault.release("gitea", "GITEA_TOKEN",
"gitea.tose.top",
"admin/chv-sync-test", "push")
self.assertEqual(self.vault.consume(rel.release_id), TOKEN)
with self.assertRaises(AuthdRefusal) as ctx:
self.vault.consume(rel.release_id)
self.assertEqual(ctx.exception.kind, "release_unknown")
with self.assertRaises(AuthdRefusal) as ctx:
self.vault.release("gitea", "GITEA_TOKEN",
"other.example",
"admin/chv-sync-test", "push")
self.assertEqual(ctx.exception.kind, "scope_mismatch")
with self.assertRaises(AuthdRefusal) as ctx:
self.vault.release("gitea", "GITEA_TOKEN",
"gitea.tose.top",
"admin/other-repo", "push")
self.assertEqual(ctx.exception.kind, "scope_mismatch")
with self.assertRaises(AuthdRefusal) as ctx:
self.vault.release("gitea", "GITEA_TOKEN",
"gitea.tose.top",
"admin/chv-sync-test", "delete")
self.assertEqual(ctx.exception.kind, "operation_not_allowed")
def test_release_expires_after_ttl(self):
# 替身过期:300 秒 TTL,301 秒后兑现即废。
self._stored()
rel = self.vault.release("gitea", "GITEA_TOKEN",
"gitea.tose.top",
"admin/chv-sync-test", "pull")
self.now[0] += 301
with self.assertRaises(AuthdRefusal) as ctx:
self.vault.consume(rel.release_id)
self.assertEqual(ctx.exception.kind, "release_expired")
def test_release_unexpired_still_works(self):
self._stored()
rel = self.vault.release("gitea", "GITEA_TOKEN",
"gitea.tose.top",
"admin/chv-sync-test", "clone")
self.now[0] += 299
self.assertEqual(self.vault.consume(rel.release_id), TOKEN)
def test_forget_revokes_pending_releases(self):
self._stored()
rel = self.vault.release("gitea", "GITEA_TOKEN",
"gitea.tose.top",
"admin/chv-sync-test", "pull")
self.assertTrue(self.vault.forget("gitea", "GITEA_TOKEN"))
self.assertFalse(self.vault.has("gitea", "GITEA_TOKEN"))
self.assertFalse(self.vault.forget("gitea", "GITEA_TOKEN"))
with self.assertRaises(AuthdRefusal) as ctx:
self.vault.consume(rel.release_id)
self.assertEqual(ctx.exception.kind, "release_unknown")
with self.assertRaises(AuthdRefusal) as ctx:
self.vault.release("gitea", "GITEA_TOKEN",
"gitea.tose.top",
"admin/chv-sync-test", "pull")
self.assertEqual(ctx.exception.kind, "credential_not_stored")
def test_repr_and_audit_never_contain_value(self):
# 真 token 泄露面:repr/审计记录里绝无 token 值。
self._stored()
rel = self.vault.release("gitea", "GITEA_TOKEN",
"gitea.tose.top",
"admin/chv-sync-test", "push")
for text in (repr(self.vault), repr(rel),
json.dumps(self.vault.audit_release(rel)),
json.dumps(self.vault.audit_refusal(
"scope_mismatch", "gitea", "GITEA_TOKEN"))):
self.assertNotIn(TOKEN, text)
self.assertNotEqual(surrogate_fingerprint(TOKEN), TOKEN)
self.assertEqual(len(surrogate_fingerprint(TOKEN)), 8)
self.assertEqual(surrogate_fingerprint(TOKEN),
surrogate_fingerprint(TOKEN))
class MintRevokeTest(unittest.TestCase):
def test_mint_returns_new_token_and_failure_is_refusal(self):
def fake_post(url, headers, payload):
self.assertIn("/users/admin/tokens", url)
self.assertEqual(payload["scopes"], ["write:repository"])
return 201, {"sha1": "new-short-token"}
self.assertEqual(
mint_gitea_token("https://gitea.tose.top/api/v1", "admin",
TOKEN, "chv-test", post=fake_post),
"new-short-token")
with self.assertRaises(AuthdRefusal) as ctx:
mint_gitea_token("https://gitea.tose.top/api/v1", "admin",
TOKEN, "chv-test",
post=lambda u, h, p: (403, {}))
self.assertEqual(ctx.exception.kind, "mint_failed")
with self.assertRaises(AuthdRefusal):
mint_gitea_token("", "admin", TOKEN, "x")
def test_revoke_statuses(self):
base = ("https://gitea.tose.top/api/v1", "admin", TOKEN)
self.assertTrue(revoke_gitea_token(
*base, 49, delete=lambda u, h: 204))
self.assertTrue(revoke_gitea_token(
*base, 49, delete=lambda u, h: 404))
self.assertFalse(revoke_gitea_token(
*base, 49, delete=lambda u, h: 500))
self.assertFalse(revoke_gitea_token("", "admin", TOKEN, 49))
# -- (2) Sentinel 决策 ----------------------------------------------------
class SentinelTest(unittest.TestCase):
def _req(self, host="gitea.tose.top", method="GET",
scheme="https", port=443, path="/api/v1/version"):
return EgressRequest("gitea", method, scheme, host, port, path)
def test_allow_public_ip_allowlisted_host(self):
policy = SentinelPolicy(allow_hosts={"gitea.tose.top"})
dec = policy.evaluate(self._req(), resolve=_public_resolver)
self.assertEqual(dec.verdict, "allow")
self.assertIsNotNone(dec.capability)
self.assertNotIn(TOKEN, json.dumps(dec.audit(self._req())))
def test_ssrf_private_ip_denied_even_when_allowlisted(self):
# SSRF 挡截:白名单主机解析到私网 IP 仍 deny。
policy = SentinelPolicy(allow_hosts={"gitea.tose.top"})
for ip in ("10.0.0.5", "192.168.1.1", "127.0.0.1",
"169.254.1.2", "100.64.0.9", "::1", "0.0.0.0"):
dec = policy.evaluate(
self._req(), resolve=lambda h, p, ip=ip: [ip])
self.assertEqual(dec.verdict, "deny", ip)
self.assertIn("ssrf_private_ip", dec.reasons[0])
def test_dns_failure_is_deny_not_ask(self):
policy = SentinelPolicy(allow_hosts={"gitea.tose.top"})
dec = policy.evaluate(
self._req(), resolve=lambda h, p: (_ for _ in ()).throw(
OSError("boom")))
self.assertEqual(dec.verdict, "deny")
self.assertEqual(dec.reasons, ["dns_resolution_failed"])
dec = policy.evaluate(self._req(), resolve=lambda h, p: [])
self.assertEqual(dec.verdict, "deny")
def test_unknown_host_is_ask_not_allow(self):
policy = SentinelPolicy(allow_hosts={"gitea.tose.top"})
dec = policy.evaluate(self._req(host="other.example"),
resolve=_public_resolver)
self.assertEqual(dec.verdict, "ask")
self.assertEqual(dec.reasons, ["host_not_allowlisted"])
self.assertIsNone(dec.capability)
def test_deny_paths(self):
policy = SentinelPolicy(allow_hosts={"gitea.tose.top"},
deny_hosts={"bad.example"})
cases = [
(EgressRequest("github", "GET", "https", "gitea.tose.top",
443, "/api/v1/version"),
"unknown_connector"),
(self._req(scheme="http"), "scheme_not_allowed"),
(self._req(port=80), "port_not_allowed"),
(self._req(host="bad.example"), "host_denied"),
(self._req(path="/login"), "path_not_allowed"),
]
for req, reason in cases:
dec = policy.evaluate(req, resolve=_public_resolver)
self.assertEqual(dec.verdict, "deny", reason)
self.assertEqual(dec.reasons, [reason])
# git 智能 HTTP 三条路径放行。
for method, path in (
("GET", "/admin/r.git/info/refs"),
("POST", "/admin/r.git/git-upload-pack"),
("POST", "/admin/r.git/git-receive-pack")):
dec = policy.evaluate(
self._req(method=method, path=path),
resolve=_public_resolver)
self.assertEqual(dec.verdict, "allow", path)
def test_capability_binding_expiry_and_uses(self):
policy = SentinelPolicy(allow_hosts={"gitea.tose.top"})
dec = policy.evaluate(self._req(), resolve=_public_resolver,
now=1000.0)
cap = dec.capability
req = self._req()
self.assertTrue(cap.matches(req, now=1000.0))
self.assertFalse(cap.matches(self._req(host="other.example"),
now=1000.0))
self.assertFalse(cap.matches(self._req(method="POST"),
now=1000.0))
self.assertFalse(cap.matches(self._req(path="/login"),
now=1000.0))
self.assertFalse(cap.matches(req, now=1000.0 + 301))
self.assertTrue(cap.use(req, now=1000.0))
self.assertFalse(cap.use(req, now=1000.0))
def test_from_env_and_approval_queue(self):
policy = SentinelPolicy.from_env(
{"CHV_SENTINEL_ALLOW_HOSTS": "a.example, b.example "},
extra_allow_hosts={"c.example"})
self.assertEqual(policy.allow_hosts,
{"a.example", "b.example", "c.example"})
queue = ApprovalQueue(clock=lambda: 1000.0)
req = self._req(host="other.example")
pid = queue.record(req, ["host_not_allowlisted"])
pending = queue.list_pending()
self.assertEqual(len(pending), 1)
self.assertEqual(pending[0]["pending_id"], pid)
self.assertEqual(pending[0]["ts"], 1000.0)
self.assertTrue(queue.resolve(pid, "deny"))
self.assertFalse(queue.resolve(pid, "deny"))
self.assertFalse(queue.resolve("nope", "allow"))
self.assertEqual(queue.list_pending(), [])
# -- (3) seed 拆分 ---------------------------------------------------------
class SeedSplitTest(unittest.TestCase):
def test_split_authd_env(self):
authd, rest = split_authd_env(
{"GITEA_TOKEN": TOKEN, "SUB2API_API_KEY": "gw",
"OTHER": "1"})
self.assertEqual(authd, {"GITEA_TOKEN": TOKEN})
self.assertEqual(rest, {"SUB2API_API_KEY": "gw", "OTHER": "1"})
self.assertEqual(split_authd_env(None), ({}, {}))
self.assertEqual(split_authd_env({}), ({}, {}))
def test_write_seed_refuses_token_in_guest_env(self):
with tempfile.TemporaryDirectory() as tmp:
seed_dir = os.path.join(tmp, "seed")
with self.assertRaises(ValueError):
write_seed(seed_dir, "vm1", "PRIV",
"ssh-ed25519 AAAAx",
secret_env={"GITEA_TOKEN": TOKEN})
with self.assertRaises(ValueError):
write_seed(seed_dir, "vm1", "PRIV",
"ssh-ed25519 AAAAx",
guest_env={"GITEA_TOKEN": TOKEN})
with self.assertRaises(ValueError):
write_seed(seed_dir, "vm1", "PRIV",
"ssh-ed25519 AAAAx",
authd_env={"SUB2API_API_KEY": "gw"})
def test_write_seed_authd_entry_is_separate_0600(self):
import yaml
with tempfile.TemporaryDirectory() as tmp:
seed_dir = os.path.join(tmp, "seed")
write_seed(seed_dir, "vm1", "PRIV", "ssh-ed25519 AAAAx",
secret_env={"SUB2API_API_KEY": "gw-key"},
guest_env={"GITEA_URL": "https://gitea.tose.top"},
authd_env={"GITEA_TOKEN": TOKEN})
with open(os.path.join(seed_dir, "user-data")) as fh:
parsed = yaml.safe_load(fh.read())
entries = {e["path"]: e for e in parsed["write_files"]}
self.assertEqual(entries[AUTHD_SEED_PATH]["permissions"],
"0600")
self.assertIn("GITEA_TOKEN=" + TOKEN,
entries[AUTHD_SEED_PATH]["content"])
self.assertNotIn("GITEA_TOKEN",
entries["/etc/chv/guest-env"]["content"])
def test_write_seed_authd_entry_carries_sync_keys(self):
# round-11 真机修复(层 2):authd-seed 须同时有
# GITEA_URL/GITEA_USER/SYNC_REPO,build_server 才会放行
#(只 GITEA_TOKEN 时 ValueError,探针 fixprobe3 即从
# guest-env 补这三键);三键只在 guest-env 里有才抄。
import yaml
with tempfile.TemporaryDirectory() as tmp:
seed_dir = os.path.join(tmp, "seed")
write_seed(seed_dir, "vm1", "PRIV", "ssh-ed25519 AAAAx",
guest_env={
"GITEA_URL": "https://gitea.tose.top",
"GITEA_USER": "admin",
"SYNC_REPO": "admin/chv-sync-test",
"SYNC_BRANCH": "main"},
authd_env={"GITEA_TOKEN": TOKEN})
with open(os.path.join(seed_dir, "user-data")) as fh:
parsed = yaml.safe_load(fh.read())
entries = {e["path"]: e for e in parsed["write_files"]}
content = entries[AUTHD_SEED_PATH]["content"]
for key in ("GITEA_URL=https://gitea.tose.top",
"GITEA_USER=admin",
"SYNC_REPO=admin/chv-sync-test",
"GITEA_TOKEN="):
self.assertIn(key, content)
# 非同步键不进 authd-seed。
self.assertNotIn("SYNC_BRANCH", content)
def test_write_seed_authd_entry_without_sync_keys(self):
# guest-env 里没有三键时 authd-seed 只装密钥,不编默认值。
import yaml
with tempfile.TemporaryDirectory() as tmp:
seed_dir = os.path.join(tmp, "seed")
write_seed(seed_dir, "vm1", "PRIV", "ssh-ed25519 AAAAx",
authd_env={"GITEA_TOKEN": TOKEN})
with open(os.path.join(seed_dir, "user-data")) as fh:
parsed = yaml.safe_load(fh.read())
entries = {e["path"]: e for e in parsed["write_files"]}
content = entries[AUTHD_SEED_PATH]["content"]
self.assertIn("GITEA_TOKEN=", content)
self.assertNotIn("GITEA_URL", content)
self.assertNotIn("GITEA_USER", content)
self.assertNotIn("SYNC_REPO", content)
# -- (4) wake 预审 -----------------------------------------------------------
class _FakeResult:
def __init__(self, returncode=0, stdout="", stderr=""):
self.returncode = returncode
self.stdout = stdout
self.stderr = stderr
class _FakeProc:
def __init__(self, alive=True):
self._alive = alive
def poll(self):
return None if self._alive else 1
def terminate(self):
self._alive = False
def wait(self, timeout=None):
return 0
def kill(self):
self._alive = False
def _write_file(path, content=b"x"):
with open(path, "wb") as fh:
fh.write(content)
os.chmod(path, 0o755)
return path
class WakeGateTest(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.dir = self.tmp.name
self.binary = _write_file(
os.path.join(self.dir, "cloud-hypervisor"))
self.ch_remote = _write_file(os.path.join(self.dir, "ch-remote"))
self.kernel = _write_file(os.path.join(self.dir, "vmlinux"))
self.rootfs = _write_file(os.path.join(self.dir, "disk.raw"),
b"R" * 100)
self.ssh_key = _write_file(os.path.join(self.dir, "id_chv"))
self.kvm = _write_file(os.path.join(self.dir, "kvm"))
self.cpuinfo = os.path.join(self.dir, "cpuinfo")
with open(self.cpuinfo, "w") as fh:
fh.write("flags : fpu vmx sse\n")
self.state_dir = os.path.join(self.dir, "state")
self.audit = []
self.launches = []
def tearDown(self):
self.tmp.cleanup()
def _runner(self, cmd, timeout=None):
if cmd and cmd[0] == "ssh-keygen":
priv = cmd[cmd.index("-f") + 1]
with open(priv, "w") as fh:
fh.write("-----BEGIN OPENSSH PRIVATE KEY-----\nFAKE\n")
os.chmod(priv, 0o600)
with open(priv + ".pub", "w") as fh:
fh.write("ssh-ed25519 AAAAfake1 vm-key\n")
return _FakeResult(0, "", "")
if cmd and cmd[0] == "genisoimage":
out = cmd[cmd.index("-output") + 1]
with open(out, "wb") as fh:
fh.write(b"ISO")
return _FakeResult(0, "", "")
return _FakeResult(0, "", "")
def _make_exec(self, env_extra=None, resolver=None):
env = {"CHV_EGRESS_PROXY": "http://10.0.0.1:8888",
"CHV_HOST_KEY_MODE": "provisioned",
"CHV_SSH_FORWARD": "1"}
if env_extra:
env.update(env_extra)
ex = CloudHypervisorExecutor(
binary=self.binary, ch_remote=self.ch_remote,
kernel=self.kernel, rootfs=self.rootfs,
ssh_key=self.ssh_key, kvm_path=self.kvm,
cpuinfo_path=self.cpuinfo, state_dir=self.state_dir,
runner=self._runner,
launcher=lambda cmd: self.launches.append(list(cmd))
or _FakeProc(alive=True),
api_client=lambda s, m, p, payload=None: (200, {}),
sleeper=lambda s: None, env=env,
sentinel_resolver=resolver)
ex.audit_callback = lambda e, t, d, k=None: self.audit.append(
(e, t.get("id") if isinstance(t, dict) else t, d, k))
return ex
def test_wake_allowlisted_gitea_passes_gate(self):
ex = self._make_exec(
env_extra={"CHV_SENTINEL_ALLOW_HOSTS": "gitea.tose.top"},
resolver=_public_resolver)
out = ex.wake_vm({"id": "gate1", "gitea_sync": dict(FAKE_SYNC)})
self.assertEqual(out["state"], "running")
kinds = [a[0] for a in self.audit]
self.assertIn("sentinel_egress_allow", kinds)
def test_wake_unknown_gitea_host_is_ask_and_fail_closed(self):
ex = self._make_exec(resolver=_public_resolver)
sync = dict(FAKE_SYNC)
sync["GITEA_URL"] = "https://other.example"
with self.assertRaises(ChvFailure) as ctx:
ex.wake_vm({"id": "gate2", "gitea_sync": sync})
self.assertEqual(ctx.exception.kind, "sentinel_egress_denied")
self.assertEqual(self.launches, [])
kinds = [a[0] for a in self.audit]
self.assertIn("sentinel_egress_ask", kinds)
pending = ex._sentinel_queue.list_pending()
self.assertEqual(len(pending), 1)
self.assertEqual(pending[0]["host"], "other.example")
def test_wake_gitea_resolving_to_private_ip_is_deny(self):
ex = self._make_exec(
env_extra={"CHV_SENTINEL_ALLOW_HOSTS": "gitea.tose.top"},
resolver=lambda h, p: ["10.0.0.5"])
with self.assertRaises(ChvFailure) as ctx:
ex.wake_vm({"id": "gate3", "gitea_sync": dict(FAKE_SYNC)})
self.assertEqual(ctx.exception.kind, "sentinel_egress_denied")
kinds = [a[0] for a in self.audit]
self.assertIn("sentinel_egress_denied", kinds)
self.assertEqual(ex._sentinel_queue.list_pending(), [])
def test_wake_without_sync_skips_gate(self):
ex = self._make_exec(resolver=_public_resolver)
out = ex.wake_vm({"id": "gate4"})
self.assertEqual(out["state"], "running")
kinds = [a[0] for a in self.audit]
self.assertNotIn("sentinel_egress_allow", kinds)
self.assertNotIn("sentinel_egress_denied", kinds)
self.assertNotIn("sentinel_egress_ask", kinds)
# -- (5) 客内 authd 守护进程 + helper 端到端 -------------------------------
class GuestAuthdTest(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.dir = self.tmp.name
self.seed_path = os.path.join(self.dir, "authd-seed")
with open(self.seed_path, "w") as fh:
fh.write("GITEA_URL=https://gitea.tose.top\n"
"GITEA_USER=admin\n"
"GITEA_TOKEN=" + TOKEN + "\n"
"SYNC_REPO=admin/chv-sync-test\n")
self.socket_path = os.path.join(self.dir, "authd.sock")
self.log_path = os.path.join(self.dir, "authd.log")
mod = _load_guest_authd()
self.server = mod.build_server(
seed_path=self.seed_path, socket_path=self.socket_path,
log_path=self.log_path, lib_dir=str(REPO_ROOT),
allowed_uids=[os.getuid()], resolve=_public_resolver)
self.thread = threading.Thread(
target=self.server.serve_forever, daemon=True)
self.thread.start()
def tearDown(self):
self.server.shutdown()
self.server.server_close()
self.tmp.cleanup()
def _ask(self, req):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.settimeout(5)
try:
sock.connect(self.socket_path)
sock.sendall((json.dumps(req) + "\n").encode("utf-8"))
buf = b""
while b"\n" not in buf:
chunk = sock.recv(4096)
if not chunk:
break
buf += chunk
return json.loads(buf.decode("utf-8"))
finally:
sock.close()
def test_release_for_clone_path(self):
resp = self._ask({"connector": "gitea",
"host": "gitea.tose.top",
"path": "/admin/chv-sync-test.git/info/refs"})
self.assertTrue(resp["ok"])
self.assertEqual(resp["username"], "admin")
self.assertEqual(resp["password"], TOKEN)
self.assertGreater(resp["expires_at"], 0)
def test_second_request_gets_fresh_release(self):
for _ in range(2):
resp = self._ask({"connector": "gitea",
"host": "gitea.tose.top",
"path": "/admin/chv-sync-test.git/"
"git-upload-pack"})
self.assertTrue(resp["ok"])
self.assertEqual(resp["password"], TOKEN)
def test_refused_paths_hosts_and_repos(self):
cases = [
({"connector": "github", "host": "gitea.tose.top",
"path": "/admin/chv-sync-test.git/info/refs"},
"bad_request_fields"),
({"connector": "gitea", "host": "gitea.tose.top",
"path": "/admin/chv-sync-test.git/foo"},
"path_not_allowed"),
({"connector": "gitea", "host": "other.example",
"path": "/admin/chv-sync-test.git/info/refs"},
"sentinel_ask"),
({"connector": "gitea", "host": "gitea.tose.top",
"path": "/foo.git/info/refs"}, "repo_unparseable"),
]
for req, error in cases:
resp = self._ask(req)
self.assertFalse(resp["ok"], req)
self.assertEqual(resp["error"], error, req)
def test_audit_log_never_contains_value(self):
self._ask({"connector": "gitea", "host": "gitea.tose.top",
"path": "/admin/chv-sync-test.git/info/refs"})
self._ask({"connector": "gitea", "host": "gitea.tose.top",
"path": "/admin/chv-sync-test.git/foo"})
with open(self.log_path) as fh:
self.assertNotIn(TOKEN, fh.read())
def test_incomplete_or_missing_seed_fails_fast(self):
mod = _load_guest_authd()
bad_seed = os.path.join(self.dir, "bad-seed")
with open(bad_seed, "w") as fh:
fh.write("GITEA_URL=https://gitea.tose.top\n")
with self.assertRaises(ValueError):
mod.build_server(seed_path=bad_seed,
socket_path=os.path.join(self.dir, "s2"),
log_path=self.log_path,
lib_dir=str(REPO_ROOT),
allowed_uids=[os.getuid()],
resolve=_public_resolver)
with self.assertRaises(FileNotFoundError):
mod.build_server(seed_path=os.path.join(self.dir, "nope"),
socket_path=os.path.join(self.dir, "s3"),
log_path=self.log_path,
lib_dir=str(REPO_ROOT),
allowed_uids=[os.getuid()],
resolve=_public_resolver)
class HelperE2ETest(unittest.TestCase):
"""helper + 真 git credential fill(假 authd socket 在后台)。"""
HELPER = REPO_ROOT / "chv-guest" / "git-credential-chv-authd"
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.dir = self.tmp.name
self.guest = GuestAuthdTest("test_release_for_clone_path")
self.guest.setUp()
self.socket_path = self.guest.socket_path
def tearDown(self):
self.guest.tearDown()
self.tmp.cleanup()
def _helper_env(self):
env = dict(os.environ)
env["CHV_AUTHD_SOCKET"] = self.socket_path
return env
def test_helper_returns_credential(self):
proc = subprocess.run(
[sys.executable, str(self.HELPER)],
input="protocol=https\nhost=gitea.tose.top\n"
"path=/admin/chv-sync-test.git/info/refs"
"?service=git-upload-pack\n\n",
capture_output=True, text=True, env=self._helper_env(),
check=False)
self.assertEqual(proc.returncode, 0, proc.stderr)
self.assertIn("username=admin", proc.stdout)
self.assertIn("password=" + TOKEN, proc.stdout)
self.assertIn("password_expiry_utc=", proc.stdout)
def test_helper_refusal_is_exit_1(self):
proc = subprocess.run(
[sys.executable, str(self.HELPER)],
input="protocol=https\nhost=gitea.tose.top\n"
"path=/admin/chv-sync-test.git/foo\n\n",
capture_output=True, text=True, env=self._helper_env(),
check=False)
self.assertEqual(proc.returncode, 1)
self.assertIn("path_not_allowed", proc.stderr)
def test_git_credential_fill_end_to_end(self):
env = self._helper_env()
env["GIT_CONFIG_NOSYSTEM"] = "1"
proc = subprocess.run(
["git", "-c", "credential.helper=%s" % self.HELPER,
"-c", "credential.useHttpPath=true",
"credential", "fill"],
input="protocol=https\nhost=gitea.tose.top\n"
"path=/admin/chv-sync-test.git/info/refs\n\n",
capture_output=True, text=True, env=env, cwd=self.dir,
check=False)
self.assertEqual(proc.returncode, 0, proc.stderr)
self.assertIn("password=" + TOKEN, proc.stdout)
if __name__ == "__main__":
unittest.main()